{
  "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": "admin.spec.js",
      "file": "admin.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Admin — Smoke",
          "file": "admin.spec.js",
          "line": 10,
          "column": 6,
          "specs": [
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 556,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:09.449Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-2e7998d85724cf3388e6",
              "file": "admin.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 207,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.224Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-88be95d030aa1ef4aff7",
              "file": "admin.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "sw.js contains 360admin-v7",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 188,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.462Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-4783c1aa1979b4f5652b",
              "file": "admin.spec.js",
              "line": 23,
              "column": 3
            },
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 421,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:34.471Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-5b2b7da8ebd71413d5b7",
              "file": "admin.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 193,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.105Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-156f52b806b66ccd5eb5",
              "file": "admin.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "sw.js contains 360admin-v7",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 212,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.318Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-bbce4cfdb0e60df71548",
              "file": "admin.spec.js",
              "line": 23,
              "column": 3
            }
          ]
        },
        {
          "title": "Admin — HTML Structure",
          "file": "admin.spec.js",
          "line": 31,
          "column": 6,
          "specs": [
            {
              "title": "Client Zones tile exists on home screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 53,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.670Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-fb639d3f7a341687d175",
              "file": "admin.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "zones screen and modal exist in DOM",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 17,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.740Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-23e73c632164a1c90343",
              "file": "admin.spec.js",
              "line": 41,
              "column": 3
            },
            {
              "title": "city chip CSS classes present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 20,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.768Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-3845955eb10f91cc8e11",
              "file": "admin.spec.js",
              "line": 53,
              "column": 3
            },
            {
              "title": "existing screens still present (no regression)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.801Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-84c21a954ca532cb7d1c",
              "file": "admin.spec.js",
              "line": 62,
              "column": 3
            },
            {
              "title": "Client Zones tile exists on home screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 63,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.545Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-c803be35369ea32666c1",
              "file": "admin.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "zones screen and modal exist in DOM",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 23,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.622Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-8e10d1340b66b524e3db",
              "file": "admin.spec.js",
              "line": 41,
              "column": 3
            },
            {
              "title": "city chip CSS classes present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.657Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-08a8955bfdf74caae94b",
              "file": "admin.spec.js",
              "line": 53,
              "column": 3
            },
            {
              "title": "existing screens still present (no regression)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.679Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-86c0df6789935e8a4935",
              "file": "admin.spec.js",
              "line": 62,
              "column": 3
            }
          ]
        },
        {
          "title": "Admin — Client Zone CRUD",
          "file": "admin.spec.js",
          "line": 75,
          "column": 6,
          "specs": [
            {
              "title": "source contains all zone management functions",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.831Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-1bc3dfbd85f92e6fd55d",
              "file": "admin.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "source contains CLI_H and CLI_RD client schema headers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.867Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-74f0c63c37aeb5df8fba",
              "file": "admin.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "delete guard checks client.accounts before deleting zone",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 23,
                      "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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:106:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 106
                        },
                        "snippet": "  104 |     const src = await resp.text();\n  105 |     expect(src).toContain('accounts?zone=eq.');\n> 106 |     expect(src).toContain('zone is assigned to one or more client accounts');\n      |                 ^\n  107 |   });\n  108 |\n  109 |   test('zone modal auto-uppercases name input', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 106
                          },
                          "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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  104 |     const src = await resp.text();\n  105 |     expect(src).toContain('accounts?zone=eq.');\n> 106 |     expect(src).toContain('zone is assigned to one or more client accounts');\n      |                 ^\n  107 |   });\n  108 |\n  109 |   test('zone modal auto-uppercases name input', async ({}) => {\n    at /var/www/360lm/tests/admin.spec.js:106:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:10.891Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--de039-counts-before-deleting-zone-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 106
                      }
                    },
                    {
                      "workerIndex": 1,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 78,
                      "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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:106:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 106
                        },
                        "snippet": "  104 |     const src = await resp.text();\n  105 |     expect(src).toContain('accounts?zone=eq.');\n> 106 |     expect(src).toContain('zone is assigned to one or more client accounts');\n      |                 ^\n  107 |   });\n  108 |\n  109 |   test('zone modal auto-uppercases name input', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 106
                          },
                          "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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  104 |     const src = await resp.text();\n  105 |     expect(src).toContain('accounts?zone=eq.');\n> 106 |     expect(src).toContain('zone is assigned to one or more client accounts');\n      |                 ^\n  107 |   });\n  108 |\n  109 |   test('zone modal auto-uppercases name input', async ({}) => {\n    at /var/www/360lm/tests/admin.spec.js:106:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-05-28T12:42:11.761Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--de039-counts-before-deleting-zone-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 106
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-ac979f5af01c8aeaf42e",
              "file": "admin.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "zone modal auto-uppercases name input",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 95,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:13.324Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-acbbbbab5bc0ba17577a",
              "file": "admin.spec.js",
              "line": 109,
              "column": 3
            },
            {
              "title": "zone screen is accessible with admin session",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1473,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:13.500Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-34e730fa40627afa7a0c",
              "file": "admin.spec.js",
              "line": 117,
              "column": 3
            },
            {
              "title": "zone modal is hidden by default",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 697,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:15.307Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-dd952bbbce25781a6d6b",
              "file": "admin.spec.js",
              "line": 131,
              "column": 3
            },
            {
              "title": "source contains all zone management functions",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 19,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.702Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-94c8e174f416b1498338",
              "file": "admin.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "source contains CLI_H and CLI_RD client schema headers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.736Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-bdf5a9532f810a2c50b7",
              "file": "admin.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "delete guard checks client.accounts before deleting zone",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 21,
                      "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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:106:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 106
                        },
                        "snippet": "  104 |     const src = await resp.text();\n  105 |     expect(src).toContain('accounts?zone=eq.');\n> 106 |     expect(src).toContain('zone is assigned to one or more client accounts');\n      |                 ^\n  107 |   });\n  108 |\n  109 |   test('zone modal auto-uppercases name input', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 106
                          },
                          "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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  104 |     const src = await resp.text();\n  105 |     expect(src).toContain('accounts?zone=eq.');\n> 106 |     expect(src).toContain('zone is assigned to one or more client accounts');\n      |                 ^\n  107 |   });\n  108 |\n  109 |   test('zone modal auto-uppercases name input', async ({}) => {\n    at /var/www/360lm/tests/admin.spec.js:106:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:35.759Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--de039-counts-before-deleting-zone-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 106
                      }
                    },
                    {
                      "workerIndex": 8,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 74,
                      "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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:106:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 106
                        },
                        "snippet": "  104 |     const src = await resp.text();\n  105 |     expect(src).toContain('accounts?zone=eq.');\n> 106 |     expect(src).toContain('zone is assigned to one or more client accounts');\n      |                 ^\n  107 |   });\n  108 |\n  109 |   test('zone modal auto-uppercases name input', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 106
                          },
                          "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\"zone is assigned to one or more client accounts\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0f172a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/admin/manifest.json\\\">\u001b[39m\n\u001b[31m<title>360LM Admin</title>\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root {\u001b[39m\n\u001b[31m  --bg:      #0f172a;\u001b[39m\n\u001b[31m  --card:    #1e293b;\u001b[39m\n\u001b[31m  --surface: #263248;\u001b[39m\n\u001b[31m  --border:  #334155;\u001b[39m\n\u001b[31m  --text:    #f1f5f9;\u001b[39m\n\u001b[31m  --muted:   #94a3b8;\u001b[39m\n\u001b[31m  --accent:  #6366f1;\u001b[39m\n\u001b[31m  --accent2: #818cf8;\u001b[39m\n\u001b[31m  --danger:  #f43f5e;\u001b[39m\n\u001b[31m  --success: #22c55e;\u001b[39m\n\u001b[31m  --warn:    #f59e0b;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\u001b[39m\n\u001b[31mbody{display:flex;flex-direction:column;max-width:480px;margin:0 auto}·\u001b[39m\n\u001b[31m/* ── SCREENS ── */\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100dvh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m/* ── TOP BAR ── */\u001b[39m\n\u001b[31m.topbar{background:var(--card);border-bottom:1px solid var(--border);\u001b[39m\n\u001b[31m  padding:14px 16px;display:flex;align-items:center;gap:10px;\u001b[39m\n\u001b[31m  position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.topbar h1{font-size:17px;font-weight:700;flex:1}\u001b[39m\n\u001b[31m.topbar .sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.back-btn{background:none;border:none;color:var(--accent2);font-size:22px;cursor:pointer;padding:2px 4px;line-height:1}\u001b[39m\n\u001b[31m.logout-btn{background:none;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:4px 8px}·\u001b[39m\n\u001b[31m/* ── CONTENT ── */\u001b[39m\n\u001b[31m.content{flex:1;padding:16px;overflow-y:auto}·\u001b[39m\n\u001b[31m/* ── LOGIN ── */\u001b[39m\n\u001b[31m#scr-login{align-items:center;justify-content:center;padding:24px}\u001b[39m\n\u001b[31m.login-box{width:100%;max-width:300px;display:flex;flex-direction:column;gap:20px;align-items:center}\u001b[39m\n\u001b[31m.app-logo{font-size:28px;font-weight:900;letter-spacing:-1px;color:var(--text)}\u001b[39m\n\u001b[31m.app-logo span{color:var(--accent)}\u001b[39m\n\u001b[31m.app-sub{font-size:13px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.login-err{color:var(--danger);font-size:13px;min-height:18px;text-align:center}\u001b[39m\n\u001b[31m.emp-select{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:10px;padding:11px 14px;font-size:15px;appearance:none;cursor:pointer}\u001b[39m\n\u001b[31m.emp-select option{background:var(--card)}·\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;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  border-radius:10px;padding:16px 8px;font-size:20px;cursor:pointer;user-select:none;transition:.1s}\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/* ── HOME TILES ── */\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:14px;\u001b[39m\n\u001b[31m  padding:20px 16px;cursor:pointer;display:flex;flex-direction:column;gap:8px;\u001b[39m\n\u001b[31m  transition:.15s;user-select:none;align-items:flex-start}\u001b[39m\n\u001b[31m.tile:active{opacity:.7}\u001b[39m\n\u001b[31m.tile-icon{font-size:28px}\u001b[39m\n\u001b[31m.tile-label{font-size:15px;font-weight:700}\u001b[39m\n\u001b[31m.tile-sub{font-size:12px;color:var(--muted)}\u001b[39m\n\u001b[31m.home-greeting{padding:20px 16px 4px;font-size:17px;font-weight:700}\u001b[39m\n\u001b[31m.home-sub{padding:0 16px 16px;font-size:13px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* ── SECTION LIST ── */\u001b[39m\n\u001b[31m.list-add{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px}\u001b[39m\n\u001b[31m.list-add h2{font-size:16px;font-weight:700}\u001b[39m\n\u001b[31m.fab{background:var(--accent);color:#fff;border:none;border-radius:24px;\u001b[39m\n\u001b[31m  padding:8px 18px;font-size:14px;font-weight:600;cursor:pointer}\u001b[39m\n\u001b[31m.fab:active{opacity:.8}·\u001b[39m\n\u001b[31m.card{background:var(--card);border:1px solid var(--border);border-radius:12px;\u001b[39m\n\u001b[31m  padding:14px;margin-bottom:10px}\u001b[39m\n\u001b[31m.card-row{display:flex;align-items:flex-start;gap:8px}\u001b[39m\n\u001b[31m.card-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.card-title{font-size:15px;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-sub{font-size:12px;color:var(--muted);line-height:1.5}\u001b[39m\n\u001b[31m.card-actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.btn-sm{background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer}\u001b[39m\n\u001b[31m.btn-sm.danger{color:var(--danger);border-color:var(--danger)}\u001b[39m\n\u001b[31m.btn-sm.accent{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.btn-sm.success{color:var(--success);border-color:var(--success)}\u001b[39m\n\u001b[31m.btn-sm:active{opacity:.7}·\u001b[39m\n\u001b[31m.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase}\u001b[39m\n\u001b[31m.badge-active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.badge-inactive{background:#450a0a;color:#fca5a5}·\u001b[39m\n\u001b[31m/* banks sub-list */\u001b[39m\n\u001b[31m.banks-list{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.bank-item{background:var(--surface);border-radius:8px;padding:10px;margin-bottom:6px;font-size:12px;line-height:1.6}\u001b[39m\n\u001b[31m.bank-item-row{display:flex;align-items:center;justify-content:space-between}\u001b[39m\n\u001b[31m.bank-name{font-weight:700;font-size:13px}\u001b[39m\n\u001b[31m.bank-actions{display:flex;gap:4px}·\u001b[39m\n\u001b[31m/* ── MODALS ── */\u001b[39m\n\u001b[31m.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);\u001b[39m\n\u001b[31m  z-index:100;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal-overlay.open{display:flex}\u001b[39m\n\u001b[31m.modal{background:var(--card);border-radius:18px 18px 0 0;padding:20px 20px 32px;\u001b[39m\n\u001b[31m  width:100%;max-width:480px;max-height:90dvh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:17px;font-weight:700;margin-bottom:16px}\u001b[39m\n\u001b[31m.modal-footer{display:flex;gap:10px;margin-top:16px}\u001b[39m\n\u001b[31m.modal-err{color:var(--danger);font-size:13px;min-height:18px;padding-top:4px}·\u001b[39m\n\u001b[31m/* ── FORM ── */\u001b[39m\n\u001b[31m.form-field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\u001b[39m\n\u001b[31m.form-field label{font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.form-field input,.form-field select,.form-field textarea{\u001b[39m\n\u001b[31m  background:var(--surface);border:1px solid var(--border);color:var(--text);\u001b[39m\n\u001b[31m  border-radius:8px;padding:10px 12px;font-size:14px;width:100%;font-family:inherit}\u001b[39m\n\u001b[31m.form-field textarea{resize:vertical;min-height:70px}\u001b[39m\n\u001b[31m.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}·\u001b[39m\n\u001b[31m/* ── PWA CHIPS ── */\u001b[39m\n\u001b[31m.pwa-chip{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border:1px solid var(--border);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;user-select:none;transition:.1s;color:var(--muted)}\u001b[39m\n\u001b[31m.pwa-chip.sel{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.pwa-chip:active{opacity:.7}\u001b[39m\n\u001b[31m.pwa-chips-wrap{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}·\u001b[39m\n\u001b[31m/* ── BUTTONS ── */\u001b[39m\n\u001b[31m.btn{border:none;border-radius:10px;padding:12px 20px;font-size:15px;font-weight:600;cursor:pointer;flex:1;transition:.1s}\u001b[39m\n\u001b[31m.btn:active{opacity:.8}\u001b[39m\n\u001b[31m.btn-primary{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-danger{background:var(--danger);color:#fff}·\u001b[39m\n\u001b[31m/* ── EMPTY / SPINNER ── */\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;margin:40px auto}\u001b[39m\n\u001b[31m@keyframes spin{to{transform:rotate(360deg)}}\u001b[39m\n\u001b[31m.empty-msg{text-align:center;color:var(--muted);padding:40px 20px;font-size:14px}·\u001b[39m\n\u001b[31m/* ── TOGGLE ── */\u001b[39m\n\u001b[31m.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.toggle-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.toggle-label{font-size:15px;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:12px;color:var(--muted);margin-top:2px}\u001b[39m\n\u001b[31m.toggle-switch{position:relative;width:44px;height:26px;flex-shrink:0}\u001b[39m\n\u001b[31m.toggle-switch input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.toggle-slider{position:absolute;inset:0;background:var(--border);border-radius:13px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.toggle-slider:before{content:'';position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.toggle-slider{background:var(--accent)}\u001b[39m\n\u001b[31minput:checked+.toggle-slider:before{transform:translateX(18px)}\u001b[39m\n\u001b[31m.settings-section{margin-bottom:20px}\u001b[39m\n\u001b[31m.settings-section-title{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;padding:0 2px}·\u001b[39m\n\u001b[31m/* ── CITY CHIPS ── */\u001b[39m\n\u001b[31m.city-chip{display:inline-flex;align-items:center;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:3px 10px;font-size:12px;font-weight:500}\u001b[39m\n\u001b[31m.city-chip-del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;margin-left:2px}\u001b[39m\n\u001b[31m.city-chip-del:hover{color:var(--danger)}\u001b[39m\n\u001b[31m.city-chips-wrap{display:flex;flex-wrap:wrap;gap:5px;min-height:28px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* ── TOAST ── */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);\u001b[39m\n\u001b[31m  background:#1e293b;color:#f1f5f9;padding:10px 20px;border-radius:8px;font-size:14px;\u001b[39m\n\u001b[31m  box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transition:opacity .25s,transform .25s;\u001b[39m\n\u001b[31m  pointer-events:none;z-index:9999;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: LOGIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen active\\\" id=\\\"scr-login\\\">\u001b[39m\n\u001b[31m  <div class=\\\"login-box\\\">\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"app-logo\\\">360LM <span>Admin</span></div>\u001b[39m\n\u001b[31m      <div class=\\\"app-sub\\\" style=\\\"margin-top:4px\\\">Restricted access — authorised personnel only</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <select class=\\\"emp-select\\\" id=\\\"login-emp\\\">\u001b[39m\n\u001b[31m      <option value=\\\"\\\">Select user…</option>\u001b[39m\n\u001b[31m      <option value=\\\"harish\\\">Harish Kumar Lal</option>\u001b[39m\n\u001b[31m      <option value=\\\"pramod\\\">Pramod Narang</option>\u001b[39m\n\u001b[31m    </select>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"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>\u001b[39m\n\u001b[31m    <div class=\\\"pin-pad\\\">\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"1\\\">1</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"2\\\">2</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"3\\\">3</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"4\\\">4</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"5\\\">5</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"6\\\">6</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"7\\\">7</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"8\\\">8</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"9\\\">9</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key clr\\\" data-k=\\\"C\\\">CLR</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key\\\" data-k=\\\"0\\\">0</button>\u001b[39m\n\u001b[31m      <button class=\\\"pin-key back\\\" data-k=\\\"B\\\">⌫</button>\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>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HOME ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-home\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m      <h1>360LM Admin</h1>\u001b[39m\n\u001b[31m      <div class=\\\"sub\\\" id=\\\"home-user-sub\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <a href=\\\"/hub/\\\" style=\\\"font-size:13px;color:var(--muted);text-decoration:none;padding:4px 8px\\\">Hub</a>\u001b[39m\n\u001b[31m    <button class=\\\"logout-btn\\\" onclick=\\\"doLogout()\\\">Sign out</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"home-greeting\\\" id=\\\"home-greeting\\\">Welcome</div>\u001b[39m\n\u001b[31m  <div class=\\\"home-sub\\\">Select an area to manage</div>\u001b[39m\n\u001b[31m  <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showCompanies()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🏢</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Companies</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Add / edit company details &amp; banks</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showEmployees()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">👥</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Employees</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Onboard, edit &amp; manage PINs</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showHrSettings()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">⚙️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">HR Settings</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Screenshot &amp; geofence config</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showAccess()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🔐</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Access</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Assign apps to any user ID</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showPwaRegistry()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗂️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">PWA Registry</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Manage mini-PWA catalogue</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"tile\\\" onclick=\\\"showZones()\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile-icon\\\">🗺️</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-label\\\">Client Zones</div>\u001b[39m\n\u001b[31m      <div class=\\\"tile-sub\\\">Zones &amp; city mapping</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: PWA REGISTRY ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-pwa-registry\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Registry</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openPwaModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"pwa-registry-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: PWA Entry ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pwa\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"pwa-modal-title\\\">Add Mini PWA</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>ID (slug)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-id\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Label</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-label\\\" placeholder=\\\"Custodian\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Icon (emoji)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"pwa-icon\\\" placeholder=\\\"🔐\\\" style=\\\"width:80px\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Sort Order</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"pwa-sort\\\" placeholder=\\\"35\\\" min=\\\"1\\\" max=\\\"999\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>URL (leave blank for JS-handled tiles)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-url\\\" placeholder=\\\"/finance/custodian/\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Access Group</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-access-group\\\" placeholder=\\\"custodian\\\" autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Employees are granted groups (not individual entries). Use same group for related mini-PWAs.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\" style=\\\"gap:16px;margin-top:4px\\\">\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-tile\\\"> Show in tile grid\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m      <label style=\\\"display:flex;align-items:center;gap:6px;font-size:13px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"checkbox\\\" id=\\\"pwa-qa\\\"> Show in quick actions\u001b[39m\n\u001b[31m      </label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m      <label>Notes (optional)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"pwa-notes\\\" placeholder=\\\"replaces finance\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pwa-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pwa')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"savePwaEntry()\\\">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<!-- ══ SCREEN: PWA ACCESS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-access\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>PWA Access</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:16px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>User ID</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"access-uid\\\" placeholder=\\\"e.g. rakesh, sachin, mohan\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/[^a-z0-9_-]/g,'')\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m        <div class=\\\"pwa-chips-wrap\\\" id=\\\"direct-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"modal-err\\\" id=\\\"access-err\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:12px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-primary\\\" style=\\\"width:100%\\\" onclick=\\\"saveDirectAccess()\\\">Save Access</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list-label\\\" style=\\\"font-size:12px;color:var(--muted);padding:0 0 8px\\\">Current assignments</div>\u001b[39m\n\u001b[31m    <div id=\\\"access-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: COMPANIES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-companies\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Companies</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openCompanyModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"companies-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: HR SETTINGS ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-hr-settings\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>HR Settings</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"hr-settings-content\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ SCREEN: EMPLOYEES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-employees\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Employees</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openEmployeeModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"employees-list\\\">\u001b[39m\n\u001b[31m    <div class=\\\"spinner\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Company ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-company\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"co-modal-title\\\">Add Company</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Short Code (unique key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-code\\\" placeholder=\\\"e.g. pms, 360dlm\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"co-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Address</label>\u001b[39m\n\u001b[31m      <textarea id=\\\"co-addr\\\" rows=\\\"2\\\" placeholder=\\\"Street / Plot / SCO\\\"></textarea>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-city\\\" placeholder=\\\"Mohali\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state\\\" placeholder=\\\"Punjab\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>State Code (GST)</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-state-code\\\" placeholder=\\\"03\\\" maxlength=\\\"2\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>GSTIN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-gstin\\\" placeholder=\\\"03AABCP…\\\" maxlength=\\\"15\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>PAN</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-pan\\\" placeholder=\\\"AABCP1234C\\\" maxlength=\\\"10\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"co-phone\\\" placeholder=\\\"+91…\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Email</label>\u001b[39m\n\u001b[31m      <input type=\\\"email\\\" id=\\\"co-email\\\" placeholder=\\\"company@email.com\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"co-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-company')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveCompany()\\\">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<!-- ══ MODAL: Bank ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-bank\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"bank-modal-title\\\">Add Bank Account</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Bank Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-name\\\" placeholder=\\\"HDFC Bank\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Account Holder Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-acc-name\\\" placeholder=\\\"Perfect Marketing Solutions\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Account Number</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-acc-no\\\" placeholder=\\\"50200…\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>IFSC Code</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"bank-ifsc\\\" placeholder=\\\"HDFC0000434\\\" maxlength=\\\"11\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Branch</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"bank-branch\\\" placeholder=\\\"SCO 85, Sector 46, Chandigarh\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"bank-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-bank')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveBank()\\\">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<!-- ══ MODAL: Employee ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-employee\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"emp-modal-title\\\">Add Employee</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-id-field\\\">\u001b[39m\n\u001b[31m      <label>System ID (unique login key)</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-id\\\" placeholder=\\\"e.g. rakesh, sachin\\\" autocomplete=\\\"off\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Full Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-name\\\" placeholder=\\\"Rakesh Kumar\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Role / Department</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-role\\\" placeholder=\\\"Designer & Installation\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"emp-designation\\\" placeholder=\\\"Sr. Designer\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Phone</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"emp-phone\\\" placeholder=\\\"+91 98765 43210\\\" inputmode=\\\"tel\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-pin-field\\\">\u001b[39m\n\u001b[31m      <label>Initial PIN (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-auth-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"emp-auth-pin\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\" id=\\\"emp-access-field\\\">\u001b[39m\n\u001b[31m      <label>Allowed PWAs</label>\u001b[39m\n\u001b[31m      <div class=\\\"pwa-chips-wrap\\\" id=\\\"emp-pwa-chips\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"emp-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-employee')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveEmployee()\\\">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<!-- ══ MODAL: Geofence Location ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-geofence\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Geofence Location</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Location Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"geo-name\\\" placeholder=\\\"e.g. Head Office, Warehouse\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Latitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lat\\\" placeholder=\\\"30.7333\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m        <label>Longitude</label>\u001b[39m\n\u001b[31m        <input type=\\\"number\\\" id=\\\"geo-lng\\\" placeholder=\\\"76.7794\\\" step=\\\"0.000001\\\" inputmode=\\\"decimal\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Radius (metres)</label>\u001b[39m\n\u001b[31m      <input type=\\\"number\\\" id=\\\"geo-radius\\\" placeholder=\\\"200\\\" min=\\\"50\\\" max=\\\"2000\\\" inputmode=\\\"numeric\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"geo-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-geofence')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveGeofenceLocation()\\\">Add</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: Reset PIN ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-pinreset\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset PIN — <span id=\\\"pinreset-name\\\"></span></div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:13px;color:var(--muted);margin-bottom:14px\\\">\u001b[39m\n\u001b[31m      Enter your own PIN to authorise this action.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>New PIN for employee (4 digits)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-new\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Your PIN (to authorise)</label>\u001b[39m\n\u001b[31m      <input type=\\\"password\\\" id=\\\"pinreset-auth\\\" maxlength=\\\"4\\\" inputmode=\\\"numeric\\\" placeholder=\\\"····\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"pinreset-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-pinreset')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"confirmPinReset()\\\">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<!-- ══ SCREEN: CLIENT ZONES ══ -->\u001b[39m\n\u001b[31m<div class=\\\"screen\\\" id=\\\"scr-zones\\\">\u001b[39m\n\u001b[31m  <div class=\\\"topbar\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('home')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>Client Zones</h1>\u001b[39m\n\u001b[31m    <button class=\\\"fab\\\" onclick=\\\"openZoneModal(null)\\\">+ Add</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"content\\\" id=\\\"zones-list\\\"><div class=\\\"spinner\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ══ MODAL: Zone (add / edit) ══ -->\u001b[39m\n\u001b[31m<div class=\\\"modal-overlay\\\" id=\\\"modal-zone\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\" id=\\\"zone-modal-title\\\">Add Zone</div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Zone Name</label>\u001b[39m\n\u001b[31m      <input type=\\\"text\\\" id=\\\"zone-name-input\\\" placeholder=\\\"e.g. NORTH, DELHI, CHANDIGARH\\\"\u001b[39m\n\u001b[31m             autocomplete=\\\"off\\\" style=\\\"text-transform:uppercase\\\"\u001b[39m\n\u001b[31m             oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_ ]/g,'')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:3px\\\">Uppercase letters, numbers, spaces. Cannot be changed after creation.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-field\\\">\u001b[39m\n\u001b[31m      <label>Cities in this zone</label>\u001b[39m\n\u001b[31m      <div class=\\\"city-chips-wrap\\\" id=\\\"zone-cities-chips\\\"></div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"zone-city-input\\\" placeholder=\\\"Type a city name, press Enter\\\"\u001b[39m\n\u001b[31m               style=\\\"flex:1\\\" onkeydown=\\\"onCityKeydown(event)\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn-sm accent\\\" onclick=\\\"addCityFromInput()\\\" style=\\\"white-space:nowrap;padding:8px 14px\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\">Press Enter or tap + Add after each city. Tap × to remove.</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-err\\\" id=\\\"zone-modal-err\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"modal-footer\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-secondary\\\" onclick=\\\"closeModal('modal-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-primary\\\" onclick=\\\"saveZone()\\\">Save Zone</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31mconst API    = '/db';\u001b[39m\n\u001b[31mconst SALE   = { 'Content-Type':'application/json','Accept-Profile':'sales','Content-Profile':'sales','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP    = { 'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation' };\u001b[39m\n\u001b[31mconst EXP_RD = { 'Content-Type':'application/json','Accept-Profile':'expense' };\u001b[39m\n\u001b[31mconst HR_RD  = { 'Content-Type':'application/json','Accept-Profile':'hr' };\u001b[39m\n\u001b[31mconst HR_WR  = { 'Content-Type':'application/json','Accept-Profile':'hr','Content-Profile':'hr','Prefer':'return=minimal' };\u001b[39m\n\u001b[31mconst SESSION_KEY = 'lm360-admin-session';\u001b[39m\n\u001b[31mconst SESSION_TTL = 8 * 3600 * 1000; // 8 hours\u001b[39m\n\u001b[31mconst ADMIN_IDS   = ['harish','pramod'];·\u001b[39m\n\u001b[31m// ── PWA Registry & Access (DB-driven) ────────────────────────────────\u001b[39m\n\u001b[31mconst HUB_RD = { 'Content-Type':'application/json', 'Accept-Profile':'hub' };\u001b[39m\n\u001b[31mconst HUB_WR = { 'Content-Type':'application/json', 'Accept-Profile':'hub', 'Content-Profile':'hub', 'Prefer':'return=minimal' };·\u001b[39m\n\u001b[31mlet _pwaList        = [];   // unique access_groups from pwa_registry (for chips)\u001b[39m\n\u001b[31mlet _pwaRegistryAll = [];   // full pwa_registry list (for registry screen)\u001b[39m\n\u001b[31mlet _empPwaSelection = [];·\u001b[39m\n\u001b[31masync function loadPwaList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?tile_eligible=eq.true&is_live=eq.true&order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) return;\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    // Deduplicate by access_group, keep first (lowest sort_order) as representative\u001b[39m\n\u001b[31m    const seen = new Set();\u001b[39m\n\u001b[31m    _pwaList = rows\u001b[39m\n\u001b[31m      .filter(p => { if (seen.has(p.access_group)) return false; seen.add(p.access_group); return true; })\u001b[39m\n\u001b[31m      .map(p => ({ id: p.access_group, label: p.label, icon: p.icon }));\u001b[39m\n\u001b[31m  } catch(_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaChips(currentAccess) {\u001b[39m\n\u001b[31m  _empPwaSelection = Array.isArray(currentAccess) ? [...currentAccess] : ['expense'];\u001b[39m\n\u001b[31m  const el = document.getElementById('emp-pwa-chips');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_empPwaSelection.includes(t.id) ? ' sel' : ''}\\\" onclick=\\\"togglePwaChip('${t.id}')\\\" data-pwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction togglePwaChip(id) {\u001b[39m\n\u001b[31m  if (_empPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _empPwaSelection = _empPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _empPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#emp-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _empPwaSelection.includes(c.dataset.pwa));\u001b[39m\n\u001b[31m  });\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[31masync function saveEmpAccess(empId, accessGroups) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}`, { method: 'DELETE', headers: HUB_WR });\u001b[39m\n\u001b[31m    if (accessGroups.length) {\u001b[39m\n\u001b[31m      const rows = accessGroups.map(g => ({ employee_id: empId, access_group: g, granted_by: currentUser.id }));\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/employee_pwa_access`, { method: 'POST', headers: HUB_WR, body: JSON.stringify(rows) });\u001b[39m\n\u001b[31m      if (!r.ok) return false;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Dual-write: keep hub-access.json in sync for offline fallback\u001b[39m\n\u001b[31m    fetch('/slides-proxy', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ type: 'save-emp-access', empId: currentUser.id, access: { [empId]: accessGroups } }),\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  } catch(_) { return false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function renderAccessList() {\u001b[39m\n\u001b[31m  const el = document.getElementById('access-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?select=employee_id,access_group&order=employee_id`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    if (!r.ok) { el.innerHTML = '<div class=\\\"empty-msg\\\">Failed to load.</div>'; return; }\u001b[39m\n\u001b[31m    const rows = await r.json();\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    rows.forEach(row => { if (!byEmp[row.employee_id]) byEmp[row.employee_id] = []; byEmp[row.employee_id].push(row.access_group); });\u001b[39m\n\u001b[31m    if (!Object.keys(byEmp).length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No assignments yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = Object.entries(byEmp).sort(([a],[b]) => a.localeCompare(b)).map(([uid, pwas]) => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\" style=\\\"font-size:13px\\\">${e(uid)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${pwas.join(', ')}</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"editDirectAccess('${e(uid)}')\\\">Edit</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(_) { el.innerHTML = '<div class=\\\"empty-msg\\\">Error loading.</div>'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA Registry management ───────────────────────────────────────────\u001b[39m\n\u001b[31mlet _editPwaId = null;·\u001b[39m\n\u001b[31masync function showPwaRegistry() {\u001b[39m\n\u001b[31m  showScreen('pwa-registry');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/pwa_registry?order=sort_order`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    _pwaRegistryAll = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m  } catch(_) { _pwaRegistryAll = []; }\u001b[39m\n\u001b[31m  renderPwaRegistry();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderPwaRegistry() {\u001b[39m\n\u001b[31m  const el = document.getElementById('pwa-registry-list');\u001b[39m\n\u001b[31m  if (!_pwaRegistryAll.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No PWAs registered.</div>'; return; }\u001b[39m\n\u001b[31m  el.innerHTML = _pwaRegistryAll.map(p => `\u001b[39m\n\u001b[31m    <div class=\\\"card\\\" style=\\\"margin-bottom:8px${p.is_live ? '' : ';opacity:.5'}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-title\\\">${p.icon} ${e(p.label)} <span style=\\\"font-size:11px;color:var(--muted)\\\">(${e(p.id)})</span></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${e(p.url || '— no URL')} · grp: <b>${e(p.access_group)}</b></div>\u001b[39m\n\u001b[31m          <div class=\\\"card-sub\\\">${p.tile_eligible ? '▣ tile' : ''}${p.qa_eligible ? ' ⚡ QA' : ''}${!p.is_live ? ' · <span style=\\\"color:var(--danger)\\\">disabled</span>' : ''}</div>\u001b[39m\n\u001b[31m          ${p.notes ? `<div class=\\\"card-sub\\\" style=\\\"color:var(--muted);font-style:italic\\\">${e(p.notes)}</div>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;flex-direction:column;gap:6px;align-items:flex-end;flex-shrink:0\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm accent\\\" onclick=\\\"openPwaModal('${e(p.id)}')\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm${p.is_live ? '' : ' accent'}\\\" onclick=\\\"togglePwaLive('${e(p.id)}',${!p.is_live})\\\">${p.is_live ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openPwaModal(id) {\u001b[39m\n\u001b[31m  _editPwaId = id || null;\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-title').textContent = id ? 'Edit PWA Entry' : 'Add Mini PWA';\u001b[39m\n\u001b[31m  document.getElementById('pwa-modal-err').textContent = '';\u001b[39m\n\u001b[31m  if (id) {\u001b[39m\n\u001b[31m    const p = _pwaRegistryAll.find(x => x.id === id);\u001b[39m\n\u001b[31m    if (!p) return;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = p.id;\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = p.label;\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = p.icon;\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = p.url || '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = p.access_group;\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = p.sort_order;\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = p.tile_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = p.qa_eligible;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = p.notes || '';\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-id').disabled = false;\u001b[39m\n\u001b[31m    document.getElementById('pwa-label').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-icon').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-url').value = '/';\u001b[39m\n\u001b[31m    document.getElementById('pwa-access-group').value = '';\u001b[39m\n\u001b[31m    document.getElementById('pwa-sort').value = '100';\u001b[39m\n\u001b[31m    document.getElementById('pwa-tile').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-qa').checked = true;\u001b[39m\n\u001b[31m    document.getElementById('pwa-notes').value = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-pwa');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function savePwaEntry() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('pwa-modal-err');\u001b[39m\n\u001b[31m  const id = document.getElementById('pwa-id').value.trim();\u001b[39m\n\u001b[31m  const label = document.getElementById('pwa-label').value.trim();\u001b[39m\n\u001b[31m  const icon = document.getElementById('pwa-icon').value.trim();\u001b[39m\n\u001b[31m  const url = document.getElementById('pwa-url').value.trim() || null;\u001b[39m\n\u001b[31m  const access_group = document.getElementById('pwa-access-group').value.trim();\u001b[39m\n\u001b[31m  const sort_order = parseInt(document.getElementById('pwa-sort').value) || 100;\u001b[39m\n\u001b[31m  const tile_eligible = document.getElementById('pwa-tile').checked;\u001b[39m\n\u001b[31m  const qa_eligible = document.getElementById('pwa-qa').checked;\u001b[39m\n\u001b[31m  const notes = document.getElementById('pwa-notes').value.trim() || null;\u001b[39m\n\u001b[31m  if (!id || !label || !icon || !access_group) { errEl.textContent = 'ID, label, icon and access group are required'; return; }\u001b[39m\n\u001b[31m  const payload = { label, icon, url, access_group, tile_eligible, qa_eligible, sort_order, notes };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editPwaId) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(_editPwaId)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify(payload) });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/pwa_registry`, { method: 'POST', headers: HUB_WR, body: JSON.stringify({ id, is_live: true, ...payload }) });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) { const err = await r.json().catch(()=>({})); errEl.textContent = err.message || 'Save failed'; return; }\u001b[39m\n\u001b[31m    closeModal('modal-pwa');\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(_editPwaId ? 'PWA entry updated' : 'PWA added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function togglePwaLive(id, live) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/pwa_registry?id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: HUB_WR, body: JSON.stringify({ is_live: live }) });\u001b[39m\n\u001b[31m    await showPwaRegistry();\u001b[39m\n\u001b[31m    toast(live ? 'PWA enabled' : 'PWA disabled');\u001b[39m\n\u001b[31m  } catch(_) { toast('Failed to update'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Registry ──────────────────────────────────────────────────·\u001b[39m\n\u001b[31mlet currentUser = null;\u001b[39m\n\u001b[31mlet pinBuffer   = '';\u001b[39m\n\u001b[31mlet _editCompanyCode = null;   // null = add mode\u001b[39m\n\u001b[31mlet _editBankId      = null;   // null = add mode\u001b[39m\n\u001b[31mlet _bankCompanyCode = null;   // which company the bank belongs to\u001b[39m\n\u001b[31mlet _editEmpId       = null;   // null = add mode\u001b[39m\n\u001b[31mlet _pinresetEmpId   = null;·\u001b[39m\n\u001b[31m// ── SESSION ──\u001b[39m\n\u001b[31mfunction saveSession(u) {\u001b[39m\n\u001b[31m  localStorage.setItem(SESSION_KEY, JSON.stringify({ ...u, loginAt: new Date().toISOString() }));\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}\u001b[39m\n\u001b[31mfunction loadSession() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const s = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');\u001b[39m\n\u001b[31m    if (!s) 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// ── SCREENS ──\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById('scr-' + id).classList.add('active');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── LOGIN ──\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[31m}·\u001b[39m\n\u001b[31mdocument.querySelectorAll('.pin-key').forEach(btn => {\u001b[39m\n\u001b[31m  btn.addEventListener('click', () => {\u001b[39m\n\u001b[31m    const k = btn.dataset.k;\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 if (pinBuffer.length < 4) { pinBuffer += k; }\u001b[39m\n\u001b[31m    updatePinDisplay();\u001b[39m\n\u001b[31m    if (pinBuffer.length === 4) attemptLogin();\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31masync function attemptLogin() {\u001b[39m\n\u001b[31m  const empId = document.getElementById('login-emp').value;\u001b[39m\n\u001b[31m  const errEl = document.getElementById('login-err');\u001b[39m\n\u001b[31m  if (!empId) { errEl.textContent = 'Select a user first'; pinBuffer = ''; updatePinDisplay(); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/verify_pin`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: EXP_RD,\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_id: empId, p_pin: pinBuffer }),\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      errEl.textContent = 'Wrong PIN — try again';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const [user] = await resp.json();\u001b[39m\n\u001b[31m    if (!ADMIN_IDS.includes(user.id)) {\u001b[39m\n\u001b[31m      pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m      errEl.textContent = 'Access restricted to authorised admin only';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\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    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error — try again';\u001b[39m\n\u001b[31m    pinBuffer = ''; updatePinDisplay();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildHome() {\u001b[39m\n\u001b[31m  document.getElementById('home-greeting').textContent = `Hello, ${currentUser.name.split(' ')[0]}`;\u001b[39m\n\u001b[31m  document.getElementById('home-user-sub').textContent = currentUser.role;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction doLogout() {\u001b[39m\n\u001b[31m  clearSession();\u001b[39m\n\u001b[31m  currentUser = null;\u001b[39m\n\u001b[31m  document.getElementById('login-emp').value = '';\u001b[39m\n\u001b[31m  pinBuffer = ''; updatePinDisplay();\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// ── COMPANIES ──\u001b[39m\n\u001b[31masync function showCompanies() {\u001b[39m\n\u001b[31m  showScreen('companies');\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshCompanies() {\u001b[39m\n\u001b[31m  const el = document.getElementById('companies-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/companies?order=name`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m    const companies = await resp.json();\u001b[39m\n\u001b[31m    if (!companies.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No companies yet. Tap \\\"+ Add\\\" to create one.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const co of companies) {\u001b[39m\n\u001b[31m      // fetch banks for this company\u001b[39m\n\u001b[31m      const bResp = await fetch(`${API}/company_banks?company_code=eq.${encodeURIComponent(co.short_code)}&order=display_order`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const banks = await bResp.json();\u001b[39m\n\u001b[31m      el.appendChild(renderCompanyCard(co, banks));\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading companies: ${e.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderCompanyCard(co, banks) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const addrLine = [co.addr, co.city, co.state].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const bankHtml = banks.map(b => `\u001b[39m\n\u001b[31m    <div class=\\\"bank-item\\\">\u001b[39m\n\u001b[31m      <div class=\\\"bank-item-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"bank-name\\\">${e(b.bank_name)}</span>\u001b[39m\n\u001b[31m        <div class=\\\"bank-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm\\\" onclick=\\\"openBankModal('${co.short_code}',${b.id})\\\">Edit</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteBank(${b.id})\\\">Del</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>A/c: <strong>${e(b.account_no)}</strong> &nbsp;IFSC: <strong>${e(b.ifsc)}</strong></div>\u001b[39m\n\u001b[31m      ${b.branch ? `<div>Branch: ${e(b.branch)}</div>` : ''}\u001b[39m\n\u001b[31m      ${b.account_name ? `<div>Name: ${e(b.account_name)}</div>` : ''}\u001b[39m\n\u001b[31m    </div>`).join('');·\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(co.name)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          Code: <strong>${e(co.short_code)}</strong>\u001b[39m\n\u001b[31m          ${co.gstin ? ` &nbsp;|&nbsp; GSTIN: ${e(co.gstin)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${addrLine ? `<div class=\\\"card-sub\\\" style=\\\"margin-top:2px\\\">${e(addrLine)}</div>` : ''}\u001b[39m\n\u001b[31m        ${co.email ? `<div class=\\\"card-sub\\\">${e(co.email)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openCompanyModal('${co.short_code}')\\\">Edit Details</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm success\\\" onclick=\\\"openBankModal('${co.short_code}',null)\\\">+ Bank</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteCompany('${co.short_code}')\\\">Delete</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${banks.length ? `<div class=\\\"banks-list\\\">${bankHtml}</div>` : ''}\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// company modal\u001b[39m\n\u001b[31mlet _companiesCache = {};\u001b[39m\n\u001b[31masync function openCompanyModal(code) {\u001b[39m\n\u001b[31m  _editCompanyCode = code;\u001b[39m\n\u001b[31m  document.getElementById('co-modal-title').textContent = code ? 'Edit Company' : 'Add Company';\u001b[39m\n\u001b[31m  document.getElementById('co-modal-err').textContent = '';\u001b[39m\n\u001b[31m  const isEdit = !!code;\u001b[39m\n\u001b[31m  document.getElementById('co-code').disabled = isEdit;·\u001b[39m\n\u001b[31m  if (isEdit) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [co] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('co-code').value       = co.short_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-name').value       = co.name || '';\u001b[39m\n\u001b[31m      document.getElementById('co-addr').value       = co.addr || '';\u001b[39m\n\u001b[31m      document.getElementById('co-city').value       = co.city || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state').value      = co.state || '';\u001b[39m\n\u001b[31m      document.getElementById('co-state-code').value = co.state_code || '';\u001b[39m\n\u001b[31m      document.getElementById('co-gstin').value      = co.gstin || '';\u001b[39m\n\u001b[31m      document.getElementById('co-pan').value        = co.pan || '';\u001b[39m\n\u001b[31m      document.getElementById('co-phone').value      = co.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('co-email').value      = co.email || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['co-code','co-name','co-addr','co-city','co-state','co-state-code','co-gstin','co-pan','co-phone','co-email']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-company');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveCompany() {\u001b[39m\n\u001b[31m  const code = document.getElementById('co-code').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g,'');\u001b[39m\n\u001b[31m  const name = document.getElementById('co-name').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('co-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Company name is required'; return; }\u001b[39m\n\u001b[31m  if (!_editCompanyCode && !code) { errEl.textContent = 'Short code is required for new company'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    addr:       document.getElementById('co-addr').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('co-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('co-state').value.trim() || null,\u001b[39m\n\u001b[31m    state_code: document.getElementById('co-state-code').value.trim() || null,\u001b[39m\n\u001b[31m    gstin:      document.getElementById('co-gstin').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:        document.getElementById('co-pan').value.trim().toUpperCase() || null,\u001b[39m\n\u001b[31m    phone:      document.getElementById('co-phone').value.trim() || null,\u001b[39m\n\u001b[31m    email:      document.getElementById('co-email').value.trim() || null,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editCompanyCode) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(_editCompanyCode)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.short_code = code;\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/companies`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-company');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(e) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + e.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteCompany(code) {\u001b[39m\n\u001b[31m  if (!confirm(`Delete company \\\"${code}\\\"? This will also delete all bank accounts for this company.`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/companies?short_code=eq.${encodeURIComponent(code)}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// bank modal\u001b[39m\n\u001b[31masync function openBankModal(companyCode, bankId) {\u001b[39m\n\u001b[31m  _bankCompanyCode = companyCode;\u001b[39m\n\u001b[31m  _editBankId = bankId;\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-title').textContent = bankId ? 'Edit Bank Account' : 'Add Bank Account';\u001b[39m\n\u001b[31m  document.getElementById('bank-modal-err').textContent = '';·\u001b[39m\n\u001b[31m  if (bankId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/company_banks?id=eq.${bankId}`, { headers: { 'Accept-Profile': 'sales' } });\u001b[39m\n\u001b[31m      const [b] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('bank-name').value     = b.bank_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-name').value = b.account_name || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-acc-no').value   = b.account_no || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-ifsc').value     = b.ifsc || '';\u001b[39m\n\u001b[31m      document.getElementById('bank-branch').value   = b.branch || '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['bank-name','bank-acc-name','bank-acc-no','bank-ifsc','bank-branch']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-bank');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBank() {\u001b[39m\n\u001b[31m  const bankName = document.getElementById('bank-name').value.trim();\u001b[39m\n\u001b[31m  const accNo    = document.getElementById('bank-acc-no').value.trim();\u001b[39m\n\u001b[31m  const ifsc     = document.getElementById('bank-ifsc').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('bank-modal-err');\u001b[39m\n\u001b[31m  if (!bankName || !accNo || !ifsc) { errEl.textContent = 'Bank name, account number and IFSC are required'; return; }·\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    bank_name:    bankName,\u001b[39m\n\u001b[31m    account_name: document.getElementById('bank-acc-name').value.trim() || null,\u001b[39m\n\u001b[31m    account_no:   accNo,\u001b[39m\n\u001b[31m    ifsc,\u001b[39m\n\u001b[31m    branch:       document.getElementById('bank-branch').value.trim() || null,\u001b[39m\n\u001b[31m    company_code: _bankCompanyCode,\u001b[39m\n\u001b[31m  };·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let resp;\u001b[39m\n\u001b[31m    if (_editBankId) {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks?id=eq.${_editBankId}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: SALE, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      resp = await fetch(`${API}/company_banks`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: SALE, body: JSON.stringify(payload),\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-bank');\u001b[39m\n\u001b[31m    await refreshCompanies();\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteBank(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this bank account?')) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/company_banks?id=eq.${id}`, { method: 'DELETE', headers: { 'Content-Profile': 'sales' } });\u001b[39m\n\u001b[31m  await refreshCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── EMPLOYEES ──\u001b[39m\n\u001b[31masync function showEmployees() {\u001b[39m\n\u001b[31m  showScreen('employees');\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshEmployees() {\u001b[39m\n\u001b[31m  const el = document.getElementById('employees-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/admin_employees?order=name`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m    const employees = await resp.json();\u001b[39m\n\u001b[31m    if (!employees.length) { el.innerHTML = '<div class=\\\"empty-msg\\\">No employees yet.</div>'; return; }\u001b[39m\n\u001b[31m    el.innerHTML = '';\u001b[39m\n\u001b[31m    for (const emp of employees) el.appendChild(renderEmployeeCard(emp));\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${err.message}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEmployeeCard(emp) {\u001b[39m\n\u001b[31m  const div = document.createElement('div');\u001b[39m\n\u001b[31m  div.className = 'card';\u001b[39m\n\u001b[31m  const badge = emp.active\u001b[39m\n\u001b[31m    ? '<span class=\\\"badge badge-active\\\">Active</span>'\u001b[39m\n\u001b[31m    : '<span class=\\\"badge badge-inactive\\\">Inactive</span>';\u001b[39m\n\u001b[31m  div.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-title\\\">${e(emp.name)} ${badge}</div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">\u001b[39m\n\u001b[31m          ID: <strong>${e(emp.id)}</strong>\u001b[39m\n\u001b[31m          ${emp.designation ? ` &nbsp;|&nbsp; ${e(emp.designation)}` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-sub\\\">${e(emp.role)}</div>\u001b[39m\n\u001b[31m        ${emp.phone ? `<div class=\\\"card-sub\\\">${e(emp.phone)}</div>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm accent\\\" onclick=\\\"openEmployeeModal('${emp.id}')\\\">Edit</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm\\\" onclick=\\\"openPinResetModal('${emp.id}','${e(emp.name)}')\\\">Reset PIN</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn-sm ${emp.active ? 'danger' : 'success'}\\\" onclick=\\\"toggleEmployeeActive('${emp.id}',${!emp.active})\\\">\u001b[39m\n\u001b[31m        ${emp.active ? 'Deactivate' : 'Activate'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `;\u001b[39m\n\u001b[31m  return div;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openEmployeeModal(empId) {\u001b[39m\n\u001b[31m  _editEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-title').textContent = empId ? 'Edit Employee' : 'Add Employee';\u001b[39m\n\u001b[31m  document.getElementById('emp-modal-err').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('emp-id-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-pin-field').style.display   = empId ? 'none' : '';\u001b[39m\n\u001b[31m  document.getElementById('emp-auth-field').style.display  = empId ? 'none' : '';·\u001b[39m\n\u001b[31m  if (empId) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/admin_employees?id=eq.${encodeURIComponent(empId)}`, { headers: { 'Accept-Profile': 'expense' } });\u001b[39m\n\u001b[31m      const [emp] = await resp.json();\u001b[39m\n\u001b[31m      document.getElementById('emp-id').value          = emp.id || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-name').value        = emp.name || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-role').value        = emp.role || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-designation').value = emp.designation || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-phone').value       = emp.phone || '';\u001b[39m\n\u001b[31m      document.getElementById('emp-pin').value         = '';\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m    // Load this employee's current access from DB\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const ar = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(empId)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m      const rows = ar.ok ? await ar.json() : [];\u001b[39m\n\u001b[31m      renderPwaChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m    } catch(_) { renderPwaChips(['expense']); }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    ['emp-id','emp-name','emp-role','emp-designation','emp-phone','emp-pin','emp-auth-pin']\u001b[39m\n\u001b[31m      .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    renderPwaChips(['expense']);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  openModal('modal-employee');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveEmployee() {\u001b[39m\n\u001b[31m  const errEl = document.getElementById('emp-modal-err');\u001b[39m\n\u001b[31m  const name  = document.getElementById('emp-name').value.trim();\u001b[39m\n\u001b[31m  const role  = document.getElementById('emp-role').value.trim();\u001b[39m\n\u001b[31m  if (!name || !role) { errEl.textContent = 'Name and role are required'; return; }·\u001b[39m\n\u001b[31m  if (_editEmpId) {\u001b[39m\n\u001b[31m    // Edit existing — PATCH allowed fields only\u001b[39m\n\u001b[31m    const payload = {\u001b[39m\n\u001b[31m      name,\u001b[39m\n\u001b[31m      role,\u001b[39m\n\u001b[31m      designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m      phone:       document.getElementById('emp-phone').value.trim() || null,\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/employees?id=eq.${encodeURIComponent(_editEmpId)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify(payload),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (!resp.ok) {\u001b[39m\n\u001b[31m        const err = await resp.json();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(_editEmpId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee saved' : 'Employee saved (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // Add new — via RPC with hashed PIN\u001b[39m\n\u001b[31m    const newId = document.getElementById('emp-id').value.trim().toLowerCase().replace(/[^a-z0-9_]/g,'');\u001b[39m\n\u001b[31m    const pin   = document.getElementById('emp-pin').value.trim();\u001b[39m\n\u001b[31m    if (!newId) { errEl.textContent = 'System ID is required'; return; }\u001b[39m\n\u001b[31m    if (!/^\\\\d{4,}$/.test(pin)) { errEl.textContent = 'PIN must be at least 4 digits'; return; }\u001b[39m\n\u001b[31m    const authPin = document.getElementById('emp-auth-pin').value.trim();\u001b[39m\n\u001b[31m    if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(`${API}/rpc/admin_add_employee`, {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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_new_id:      newId,\u001b[39m\n\u001b[31m          p_name:        name,\u001b[39m\n\u001b[31m          p_role:        role,\u001b[39m\n\u001b[31m          p_pin:         pin,\u001b[39m\n\u001b[31m          p_designation: document.getElementById('emp-designation').value.trim() || null,\u001b[39m\n\u001b[31m          p_phone:       document.getElementById('emp-phone').value.trim() || null,\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();\u001b[39m\n\u001b[31m        errEl.textContent = err.message || err.details || 'Save failed';\u001b[39m\n\u001b[31m        return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      const ok = await saveEmpAccess(newId, _empPwaSelection);\u001b[39m\n\u001b[31m      closeModal('modal-employee');\u001b[39m\n\u001b[31m      await refreshEmployees();\u001b[39m\n\u001b[31m      toast(ok ? 'Employee added' : 'Employee added (access sync failed — retry)');\u001b[39m\n\u001b[31m    } catch(err) {\u001b[39m\n\u001b[31m      errEl.textContent = 'Error: ' + err.message;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleEmployeeActive(empId, newState) {\u001b[39m\n\u001b[31m  const label = newState ? 'activate' : 'deactivate';\u001b[39m\n\u001b[31m  if (!confirm(`${label.charAt(0).toUpperCase()+label.slice(1)} ${empId}?`)) return;\u001b[39m\n\u001b[31m  await fetch(`${API}/employees?id=eq.${encodeURIComponent(empId)}`, {\u001b[39m\n\u001b[31m    method: 'PATCH', headers: { ...EXP, 'Prefer': 'return=minimal' }, body: JSON.stringify({ active: newState }),\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  await refreshEmployees();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// PIN reset\u001b[39m\n\u001b[31mfunction openPinResetModal(empId, empName) {\u001b[39m\n\u001b[31m  _pinresetEmpId = empId;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-name').textContent = empName;\u001b[39m\n\u001b[31m  document.getElementById('pinreset-new').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-auth').value = '';\u001b[39m\n\u001b[31m  document.getElementById('pinreset-err').textContent = '';\u001b[39m\n\u001b[31m  openModal('modal-pinreset');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmPinReset() {\u001b[39m\n\u001b[31m  const errEl   = document.getElementById('pinreset-err');\u001b[39m\n\u001b[31m  const newPin  = document.getElementById('pinreset-new').value.trim();\u001b[39m\n\u001b[31m  const authPin = document.getElementById('pinreset-auth').value.trim();\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(newPin))  { errEl.textContent = 'New PIN must be exactly 4 digits'; return; }\u001b[39m\n\u001b[31m  if (!/^\\\\d{4}$/.test(authPin)) { errEl.textContent = 'Your PIN must be exactly 4 digits'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/rpc/admin_set_pin`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'expense', 'Accept-Profile': 'expense' },\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: _pinresetEmpId,\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();\u001b[39m\n\u001b[31m      errEl.textContent = err.message || err.details || 'Failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-pinreset');\u001b[39m\n\u001b[31m    alert(`PIN reset successfully for ${_pinresetEmpId}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── HR SETTINGS ──\u001b[39m\n\u001b[31masync function showHrSettings() {\u001b[39m\n\u001b[31m  showScreen('hr-settings');\u001b[39m\n\u001b[31m  await loadHrSettings();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadHrSettings() {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [settingsResp, geoResp] = await Promise.all([\u001b[39m\n\u001b[31m      fetch(`${API}/hr_settings?select=key,value`, { headers: HR_RD }),\u001b[39m\n\u001b[31m      fetch(`${API}/geofence_locations?order=name`, { headers: HR_RD }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    const settings = settingsResp.ok ? await settingsResp.json() : [];\u001b[39m\n\u001b[31m    const geoLocs  = geoResp.ok ? await geoResp.json() : [];\u001b[39m\n\u001b[31m    renderHrSettings(settings, geoLocs);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error loading HR settings: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderHrSettings(settings, geoLocs) {\u001b[39m\n\u001b[31m  const el = document.getElementById('hr-settings-content');\u001b[39m\n\u001b[31m  const getSetting = key => settings.find(s => s.key === key)?.value;\u001b[39m\n\u001b[31m  const screenshotVal = getSetting('screenshot_prevention') || 'aggressive';\u001b[39m\n\u001b[31m  const screenshotOn  = screenshotVal !== 'off';·\u001b[39m\n\u001b[31m  const geoHtml = geoLocs.length\u001b[39m\n\u001b[31m    ? geoLocs.map(g => `\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-title\\\">${e(g.name)}\u001b[39m\n\u001b[31m              <span class=\\\"badge ${g.is_active ? 'badge-active' : 'badge-inactive'}\\\" style=\\\"margin-left:6px\\\">${g.is_active ? 'Active' : 'Inactive'}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div class=\\\"card-sub\\\">${g.lat}, ${g.lng} &nbsp;·&nbsp; ${g.radius_m}m radius</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"card-actions\\\">\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm ${g.is_active ? 'danger' : 'success'}\\\" onclick=\\\"toggleGeofenceActive(${g.id},${!g.is_active})\\\">${g.is_active ? 'Disable' : 'Enable'}</button>\u001b[39m\n\u001b[31m          <button class=\\\"btn-sm danger\\\" onclick=\\\"deleteGeofenceLocation(${g.id})\\\">Delete</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`).join('')\u001b[39m\n\u001b[31m    : '<div class=\\\"empty-msg\\\" style=\\\"padding:16px 0\\\">No geofence locations yet</div>';·\u001b[39m\n\u001b[31m  el.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"settings-section-title\\\">Screenshot Prevention (HR Salary Slips)</div>\u001b[39m\n\u001b[31m      <div class=\\\"card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"toggle-row\\\">\u001b[39m\n\u001b[31m          <div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-label\\\">Enable screenshot deterrence</div>\u001b[39m\n\u001b[31m            <div class=\\\"toggle-sub\\\">Adds watermark &amp; disables text selection on salary slips</div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <label class=\\\"toggle-switch\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" id=\\\"toggle-screenshot\\\" ${screenshotOn ? 'checked' : ''} onchange=\\\"saveScreenshotPrevention(this.checked)\\\">\u001b[39m\n\u001b[31m            <span class=\\\"toggle-slider\\\"></span>\u001b[39m\n\u001b[31m          </label>\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=\\\"settings-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"list-add\\\" style=\\\"padding-bottom:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"settings-section-title\\\" style=\\\"margin-bottom:0\\\">Geofence Locations</div>\u001b[39m\n\u001b[31m        <button class=\\\"fab\\\" onclick=\\\"openModal('modal-geofence')\\\">+ Add</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${geoHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveScreenshotPrevention(checked) {\u001b[39m\n\u001b[31m  const val = checked ? 'aggressive' : 'off';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/hr_settings`, {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Content-Profile': 'hr', 'Accept-Profile': 'hr',\u001b[39m\n\u001b[31m                 'Prefer': 'resolution=merge-duplicates,return=minimal' },\u001b[39m\n\u001b[31m      body: JSON.stringify({ key: 'screenshot_prevention', value: val }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) {\u001b[39m\n\u001b[31m      document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m      toast('Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    toast(checked ? 'Screenshot deterrence ON' : 'Screenshot deterrence OFF');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    document.getElementById('toggle-screenshot').checked = !checked;\u001b[39m\n\u001b[31m    toast('Network error');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleGeofenceActive(id, newVal) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', headers: HR_WR, body: JSON.stringify({ is_active: newVal }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) { toast('Update failed'); return; }\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteGeofenceLocation(id) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this geofence location?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/geofence_locations?id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'hr' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveGeofenceLocation() {\u001b[39m\n\u001b[31m  const name   = document.getElementById('geo-name').value.trim();\u001b[39m\n\u001b[31m  const lat    = parseFloat(document.getElementById('geo-lat').value);\u001b[39m\n\u001b[31m  const lng    = parseFloat(document.getElementById('geo-lng').value);\u001b[39m\n\u001b[31m  const radius = parseInt(document.getElementById('geo-radius').value, 10);\u001b[39m\n\u001b[31m  const errEl  = document.getElementById('geo-modal-err');\u001b[39m\n\u001b[31m  if (!name) { errEl.textContent = 'Location name is required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(lat) || isNaN(lng)) { errEl.textContent = 'Valid latitude and longitude are required'; return; }\u001b[39m\n\u001b[31m  if (isNaN(radius) || radius < 50) { errEl.textContent = 'Radius must be at least 50 metres'; return; }·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(`${API}/geofence_locations`, {\u001b[39m\n\u001b[31m      method: 'POST', headers: HR_WR, body: JSON.stringify({ name, lat, lng, radius_m: radius, is_active: true }),\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 || 'Save failed';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-geofence');\u001b[39m\n\u001b[31m    ['geo-name','geo-lat','geo-lng','geo-radius'].forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    await loadHrSettings();\u001b[39m\n\u001b[31m    toast('Geofence location added');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── PWA DIRECT ACCESS ──\u001b[39m\n\u001b[31mlet _directPwaSelection = [];·\u001b[39m\n\u001b[31masync function showAccess() {\u001b[39m\n\u001b[31m  showScreen('access');\u001b[39m\n\u001b[31m  renderDirectChips([]);\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = '';\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  await loadPwaList();\u001b[39m\n\u001b[31m  await renderAccessList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDirectChips(current) {\u001b[39m\n\u001b[31m  _directPwaSelection = Array.isArray(current) ? [...current] : [];\u001b[39m\n\u001b[31m  document.getElementById('direct-pwa-chips').innerHTML = _pwaList.map(t =>\u001b[39m\n\u001b[31m    `<div class=\\\"pwa-chip${_directPwaSelection.includes(t.id) ? ' sel' : ''}\\\"\u001b[39m\n\u001b[31m      onclick=\\\"toggleDirectChip('${t.id}')\\\" data-dpwa=\\\"${t.id}\\\">${t.icon} ${t.label}</div>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleDirectChip(id) {\u001b[39m\n\u001b[31m  if (_directPwaSelection.includes(id)) {\u001b[39m\n\u001b[31m    _directPwaSelection = _directPwaSelection.filter(x => x !== id);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    _directPwaSelection.push(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.querySelectorAll('#direct-pwa-chips .pwa-chip').forEach(c => {\u001b[39m\n\u001b[31m    c.classList.toggle('sel', _directPwaSelection.includes(c.dataset.dpwa));\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function editDirectAccess(uid) {\u001b[39m\n\u001b[31m  document.getElementById('access-uid').value = uid;\u001b[39m\n\u001b[31m  document.getElementById('access-err').textContent = '';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/employee_pwa_access?employee_id=eq.${encodeURIComponent(uid)}&select=access_group`, { headers: HUB_RD });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    renderDirectChips(rows.map(r => r.access_group));\u001b[39m\n\u001b[31m  } catch(_) { renderDirectChips([]); }\u001b[39m\n\u001b[31m  document.getElementById('access-uid').scrollIntoView({ behavior: 'smooth', block: 'start' });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveDirectAccess() {\u001b[39m\n\u001b[31m  const uid   = document.getElementById('access-uid').value.trim();\u001b[39m\n\u001b[31m  const errEl = document.getElementById('access-err');\u001b[39m\n\u001b[31m  if (!uid) { errEl.textContent = 'User ID is required'; return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ok = await saveEmpAccess(uid, _directPwaSelection);\u001b[39m\n\u001b[31m    if (!ok) { errEl.textContent = 'Save failed'; return; }\u001b[39m\n\u001b[31m    await renderAccessList();\u001b[39m\n\u001b[31m    errEl.textContent = '';\u001b[39m\n\u001b[31m    toast(`Access saved for ${uid}`);\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    errEl.textContent = 'Network error: ' + err.message;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End PWA Direct Access ──·\u001b[39m\n\u001b[31m// ── CLIENT ZONES ──\u001b[39m\n\u001b[31mconst CLI_H  = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLI_RD = {'Content-Type':'application/json','Accept-Profile':'client'};·\u001b[39m\n\u001b[31mlet _editZoneName  = null;\u001b[39m\n\u001b[31mlet _zoneEditCities = [];·\u001b[39m\n\u001b[31masync function showZones() {\u001b[39m\n\u001b[31m  showScreen('zones');\u001b[39m\n\u001b[31m  await refreshZones();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshZones() {\u001b[39m\n\u001b[31m  const el = document.getElementById('zones-list');\u001b[39m\n\u001b[31m  el.innerHTML = '<div class=\\\"spinner\\\"></div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?order=zone_name`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error('Failed to load');\u001b[39m\n\u001b[31m    const zones = await r.json();\u001b[39m\n\u001b[31m    if (!zones.length) {\u001b[39m\n\u001b[31m      el.innerHTML = '<div class=\\\"empty-msg\\\">No zones yet. Tap \\\"+ Add\\\" to create the first zone.</div>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    el.innerHTML = zones.map(z => {\u001b[39m\n\u001b[31m      const cityHtml = (z.cities || []).length\u001b[39m\n\u001b[31m        ? (z.cities || []).map(c => `<span class=\\\"city-chip\\\">${e(c)}</span>`).join('')\u001b[39m\n\u001b[31m        : `<span style=\\\"color:var(--muted);font-size:12px\\\">No cities added</span>`;\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m          <div class=\\\"card-row\\\">\u001b[39m\n\u001b[31m            <div class=\\\"card-info\\\">\u001b[39m\n\u001b[31m              <div class=\\\"card-title\\\">${e(z.zone_name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\\\">${cityHtml}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"display:flex;flex-direction:column;gap:6px;flex-shrink:0;margin-left:10px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm accent\\\" onclick=\\\"openZoneModal('${e(z.zone_name)}')\\\">Edit</button>\u001b[39m\n\u001b[31m              <button class=\\\"btn-sm danger\\\"  onclick=\\\"deleteZone('${e(z.zone_name)}')\\\">Delete</button>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  } catch(err) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div class=\\\"empty-msg\\\">Error: ${e(err.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openZoneModal(zoneName) {\u001b[39m\n\u001b[31m  _editZoneName   = zoneName || null;\u001b[39m\n\u001b[31m  _zoneEditCities = [];\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-title').textContent  = zoneName ? 'Edit Zone' : 'Add Zone';\u001b[39m\n\u001b[31m  document.getElementById('zone-modal-err').textContent    = '';\u001b[39m\n\u001b[31m  document.getElementById('zone-city-input').value         = '';\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('zone-name-input');\u001b[39m\n\u001b[31m  nameEl.value    = zoneName || '';\u001b[39m\n\u001b[31m  nameEl.disabled = !!zoneName;·\u001b[39m\n\u001b[31m  if (zoneName) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, { headers: CLI_RD });\u001b[39m\n\u001b[31m      const [z] = r.ok ? await r.json() : [{}];\u001b[39m\n\u001b[31m      _zoneEditCities = Array.isArray(z.cities) ? [...z.cities] : [];\u001b[39m\n\u001b[31m    } catch {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m  openModal('modal-zone');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderZoneCityChips() {\u001b[39m\n\u001b[31m  document.getElementById('zone-cities-chips').innerHTML = _zoneEditCities.map((c, i) =>\u001b[39m\n\u001b[31m    `<span class=\\\"city-chip\\\">${e(c)}<button class=\\\"city-chip-del\\\" onclick=\\\"removeCity(${i})\\\" title=\\\"Remove\\\">×</button></span>`\u001b[39m\n\u001b[31m  ).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction removeCity(idx) {\u001b[39m\n\u001b[31m  _zoneEditCities.splice(idx, 1);\u001b[39m\n\u001b[31m  renderZoneCityChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCityKeydown(ev) {\u001b[39m\n\u001b[31m  if (ev.key === 'Enter') { ev.preventDefault(); addCityFromInput(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction addCityFromInput() {\u001b[39m\n\u001b[31m  const inp = document.getElementById('zone-city-input');\u001b[39m\n\u001b[31m  const val = inp.value.trim().replace(/\\\\s+/g, ' ');\u001b[39m\n\u001b[31m  if (!val) return;\u001b[39m\n\u001b[31m  if (!_zoneEditCities.some(c => c.toLowerCase() === val.toLowerCase())) {\u001b[39m\n\u001b[31m    _zoneEditCities.push(val);\u001b[39m\n\u001b[31m    renderZoneCityChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  inp.value = '';\u001b[39m\n\u001b[31m  inp.focus();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveZone() {\u001b[39m\n\u001b[31m  const errEl    = document.getElementById('zone-modal-err');\u001b[39m\n\u001b[31m  const zoneName = document.getElementById('zone-name-input').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if (!zoneName) { errEl.textContent = 'Zone name is required'; return; }\u001b[39m\n\u001b[31m  addCityFromInput();   // flush any typed-but-not-added city·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    let r;\u001b[39m\n\u001b[31m    if (_editZoneName) {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(_editZoneName)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: CLI_H, body: JSON.stringify({ cities: _zoneEditCities }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      r = await fetch(`${API}/zones`, {\u001b[39m\n\u001b[31m        method: 'POST', headers: CLI_H,\u001b[39m\n\u001b[31m        body: JSON.stringify({ zone_name: zoneName, cities: _zoneEditCities, created_by: currentUser.id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!r.ok) {\u001b[39m\n\u001b[31m      const err = await r.json().catch(() => ({}));\u001b[39m\n\u001b[31m      errEl.textContent = err.message || (err.code === '23505' ? 'Zone name already exists' : 'Save failed');\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-zone');\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast(_editZoneName ? 'Zone updated' : 'Zone added');\u001b[39m\n\u001b[31m  } catch(err) { errEl.textContent = 'Network error: ' + err.message; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteZone(zoneName) {\u001b[39m\n\u001b[31m  // Guard: check if any client accounts reference this zone\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/accounts?zone=eq.${encodeURIComponent(zoneName)}&select=id&limit=1`, { headers: CLI_RD });\u001b[39m\n\u001b[31m    const refs = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    if (refs.length) {\u001b[39m\n\u001b[31m      alert(`Cannot delete \\\"${zoneName}\\\" — it is assigned to one or more client accounts.\\\\nUpdate or clear those accounts first.`);\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch {}\u001b[39m\n\u001b[31m  if (!confirm(`Delete zone \\\"${zoneName}\\\"? This cannot be undone.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/zones?zone_name=eq.${encodeURIComponent(zoneName)}`, {\u001b[39m\n\u001b[31m      method: 'DELETE', headers: { 'Content-Profile': 'client', 'Accept-Profile': 'client' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) { toast('Delete failed'); return; }\u001b[39m\n\u001b[31m    await refreshZones();\u001b[39m\n\u001b[31m    toast('Zone deleted');\u001b[39m\n\u001b[31m  } catch { toast('Network error'); }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m// ── End Client Zones ──·\u001b[39m\n\u001b[31m// ── MODAL HELPERS ──\u001b[39m\n\u001b[31mfunction openModal(id) { document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('open'); }·\u001b[39m\n\u001b[31m// close modals on overlay click\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal-overlay').forEach(ov => {\u001b[39m\n\u001b[31m  ov.addEventListener('click', e => { if (e.target === ov) ov.classList.remove('open'); });\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31m// ── UTILITY ──\u001b[39m\n\u001b[31mfunction e(s) {\u001b[39m\n\u001b[31m  return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── INIT ──\u001b[39m\n\u001b[31mwindow.addEventListener('load', async () => {\u001b[39m\n\u001b[31m  if ('serviceWorker' in navigator) navigator.serviceWorker.register('/admin/sw.js');\u001b[39m\n\u001b[31m  await loadPwaList().catch(() => {});·\u001b[39m\n\u001b[31m  const sess = loadSession();\u001b[39m\n\u001b[31m  if (sess && ADMIN_IDS.includes(sess.id)) {\u001b[39m\n\u001b[31m    currentUser = sess;\u001b[39m\n\u001b[31m    buildHome();\u001b[39m\n\u001b[31m    showScreen('home');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  104 |     const src = await resp.text();\n  105 |     expect(src).toContain('accounts?zone=eq.');\n> 106 |     expect(src).toContain('zone is assigned to one or more client accounts');\n      |                 ^\n  107 |   });\n  108 |\n  109 |   test('zone modal auto-uppercases name input', async ({}) => {\n    at /var/www/360lm/tests/admin.spec.js:106:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-05-28T12:42:36.678Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--de039-counts-before-deleting-zone-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 106
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-3e89a03bd0eb9f866797",
              "file": "admin.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "zone modal auto-uppercases name input",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 60,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:37.662Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-a9a4e7e7f70d2ff060d7",
              "file": "admin.spec.js",
              "line": 109,
              "column": 3
            },
            {
              "title": "zone screen is accessible with admin session",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 428,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:37.763Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-14e9a21562a239507696",
              "file": "admin.spec.js",
              "line": 117,
              "column": 3
            },
            {
              "title": "zone modal is hidden by default",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 315,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:38.344Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-0479c0e5b7dc281d56ca",
              "file": "admin.spec.js",
              "line": 131,
              "column": 3
            }
          ]
        },
        {
          "title": "Admin — DB Schema (Step 1 migrations)",
          "file": "admin.spec.js",
          "line": 138,
          "column": 6,
          "specs": [
            {
              "title": "client.zones table exists and is accessible via PostgREST",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 307,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:16.051Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-e2d62a2dca79a6c6de1f",
              "file": "admin.spec.js",
              "line": 139,
              "column": 3
            },
            {
              "title": "recce.branding_approvals table exists and is accessible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 210,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:16.392Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-a31533459baac9c54098",
              "file": "admin.spec.js",
              "line": 149,
              "column": 3
            },
            {
              "title": "client.accounts has zone and is_head columns",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 558,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:16.663Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-90b160b77ed82210f0c8",
              "file": "admin.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "installation.campaigns has zones column",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 449,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:17.261Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-a4a8018f5f4f81e146ae",
              "file": "admin.spec.js",
              "line": 167,
              "column": 3
            },
            {
              "title": "sales.jobs has campaign_id column",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 430,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:17.734Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-5cb55da05cf1ea6843f2",
              "file": "admin.spec.js",
              "line": 175,
              "column": 3
            },
            {
              "title": "client.zones table exists and is accessible via PostgREST",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 165,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:38.690Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-b14753baa812af641406",
              "file": "admin.spec.js",
              "line": 139,
              "column": 3
            },
            {
              "title": "recce.branding_approvals table exists and is accessible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 173,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:38.878Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-119c143c053c4e3b7dab",
              "file": "admin.spec.js",
              "line": 149,
              "column": 3
            },
            {
              "title": "client.accounts has zone and is_head columns",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 179,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:39.063Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-594eb74c1095c9886f73",
              "file": "admin.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "installation.campaigns has zones column",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 156,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:39.284Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-02c51d6b5da8b858d859",
              "file": "admin.spec.js",
              "line": 167,
              "column": 3
            },
            {
              "title": "sales.jobs has campaign_id column",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 151,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:39.461Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-1fcbdc35e2babd92dfe8",
              "file": "admin.spec.js",
              "line": 175,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "client.spec.js",
      "file": "client.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Client Portal — Smoke",
          "file": "client.spec.js",
          "line": 10,
          "column": 6,
          "specs": [
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1526,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:18.242Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-6673200f242f03a9618c",
              "file": "client.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 582,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:19.816Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-e2d3d1fc80d0c0634628",
              "file": "client.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "sw.js contains 360client-v4",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 447,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:20.464Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-8cc8b1ab743db39a16f6",
              "file": "client.spec.js",
              "line": 23,
              "column": 3
            },
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 278,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:39.649Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-63715a1901e190569f1a",
              "file": "client.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 307,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:39.967Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-13afabfa0cc0cf613582",
              "file": "client.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "sw.js contains 360client-v4",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 205,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.300Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-808cd900d306f71bf574",
              "file": "client.spec.js",
              "line": 23,
              "column": 3
            }
          ]
        },
        {
          "title": "Client Portal — HTML Structure",
          "file": "client.spec.js",
          "line": 31,
          "column": 6,
          "specs": [
            {
              "title": "login screen has PIN pad, Client ID tab, Company Code tab",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:20.939Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-c507f4e26462d12cbf7b",
              "file": "client.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "screens for home, recce, recce-approval, responses, acct detail all exist",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 36,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:20.964Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-b2797db2faa043b1f639",
              "file": "client.spec.js",
              "line": 42,
              "column": 3
            },
            {
              "title": "New Account modal has zone dropdown and is_head toggle",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 40,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:21.025Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-74bebc694757190e57c7",
              "file": "client.spec.js",
              "line": 55,
              "column": 3
            },
            {
              "title": "Edit Zone / Head Status modal exists",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:21.094Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-159814b2965731e8d4ee",
              "file": "client.spec.js",
              "line": 64,
              "column": 3
            },
            {
              "title": "Add Grant modal contains campaign option",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:21.122Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-4eed6b054048eab752c7",
              "file": "client.spec.js",
              "line": 73,
              "column": 3
            },
            {
              "title": "account detail has zone-row and Edit Zone button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 24,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:21.152Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-5ac6475027724d20203c",
              "file": "client.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "login screen has PIN pad, Client ID tab, Company Code tab",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.520Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-406000ccec728f206e6e",
              "file": "client.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "screens for home, recce, recce-approval, responses, acct detail all exist",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.547Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-6bc462dcc1830f1b7a50",
              "file": "client.spec.js",
              "line": 42,
              "column": 3
            },
            {
              "title": "New Account modal has zone dropdown and is_head toggle",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.573Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-d56a6e55479d626f51d6",
              "file": "client.spec.js",
              "line": 55,
              "column": 3
            },
            {
              "title": "Edit Zone / Head Status modal exists",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.605Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-97bd3eed8c02f81802a6",
              "file": "client.spec.js",
              "line": 64,
              "column": 3
            },
            {
              "title": "Add Grant modal contains campaign option",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.629Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-c85ebc4a5735290b776d",
              "file": "client.spec.js",
              "line": 73,
              "column": 3
            },
            {
              "title": "account detail has zone-row and Edit Zone button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.651Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-097f06085d77205d1c57",
              "file": "client.spec.js",
              "line": 81,
              "column": 3
            }
          ]
        },
        {
          "title": "Client Portal — Zone-Aware Access",
          "file": "client.spec.js",
          "line": 92,
          "column": 6,
          "specs": [
            {
              "title": "source contains INST_RD header for installation schema",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:21.198Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-353e7135eaaeb7691dc1",
              "file": "client.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "source contains computeAccessibleCampaigns and isHead logic",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 24,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:21.236Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-f472587cf5982a0bf062",
              "file": "client.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "session enrichment: separate account fetch for zone/isHead",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 46,
                      "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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:118:17",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 17,
                          "line": 118
                        },
                        "snippet": "  116 |     expect(src).toContain('fullAcct.zone');\n  117 |     expect(src).toContain('fullAcct.is_head');\n> 118 |     expect(src).toContain('is_head===undefined');\n      |                 ^\n  119 |   });\n  120 |\n  121 |   test('source contains _STATUS_CFG for client_status badges', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 17,
                            "line": 118
                          },
                          "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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  116 |     expect(src).toContain('fullAcct.zone');\n  117 |     expect(src).toContain('fullAcct.is_head');\n> 118 |     expect(src).toContain('is_head===undefined');\n      |                 ^\n  119 |   });\n  120 |\n  121 |   test('source contains _STATUS_CFG for client_status badges', async ({}) => {\n    at /var/www/360lm/tests/client.spec.js:118:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:21.292Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Zon-c10ce-count-fetch-for-zone-isHead-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 17,
                        "line": 118
                      }
                    },
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 269,
                      "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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:118:17",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 17,
                          "line": 118
                        },
                        "snippet": "  116 |     expect(src).toContain('fullAcct.zone');\n  117 |     expect(src).toContain('fullAcct.is_head');\n> 118 |     expect(src).toContain('is_head===undefined');\n      |                 ^\n  119 |   });\n  120 |\n  121 |   test('source contains _STATUS_CFG for client_status badges', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 17,
                            "line": 118
                          },
                          "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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  116 |     expect(src).toContain('fullAcct.zone');\n  117 |     expect(src).toContain('fullAcct.is_head');\n> 118 |     expect(src).toContain('is_head===undefined');\n      |                 ^\n  119 |   });\n  120 |\n  121 |   test('source contains _STATUS_CFG for client_status badges', async ({}) => {\n    at /var/www/360lm/tests/client.spec.js:118:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-05-28T12:42:22.968Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Zon-c10ce-count-fetch-for-zone-isHead-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 17,
                        "line": 118
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "9bf021294cad394269b8-7bcea2a67c7d740274e5",
              "file": "client.spec.js",
              "line": 112,
              "column": 3
            },
            {
              "title": "source contains _STATUS_CFG for client_status badges",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 4,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 144,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:25.041Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-c634bd6eae3b728575a7",
              "file": "client.spec.js",
              "line": 121,
              "column": 3
            },
            {
              "title": "enterRecce fetches by campaign_id not client_name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 4,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 27,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:25.273Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-c063fa211f9386cbe422",
              "file": "client.spec.js",
              "line": 131,
              "column": 3
            },
            {
              "title": "source contains INST_RD header for installation schema",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.678Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-48830869083c9fd762d5",
              "file": "client.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "source contains computeAccessibleCampaigns and isHead logic",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.700Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-5ef147e6b876c87cf672",
              "file": "client.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "session enrichment: separate account fetch for zone/isHead",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 32,
                      "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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:118:17",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 17,
                          "line": 118
                        },
                        "snippet": "  116 |     expect(src).toContain('fullAcct.zone');\n  117 |     expect(src).toContain('fullAcct.is_head');\n> 118 |     expect(src).toContain('is_head===undefined');\n      |                 ^\n  119 |   });\n  120 |\n  121 |   test('source contains _STATUS_CFG for client_status badges', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 17,
                            "line": 118
                          },
                          "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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  116 |     expect(src).toContain('fullAcct.zone');\n  117 |     expect(src).toContain('fullAcct.is_head');\n> 118 |     expect(src).toContain('is_head===undefined');\n      |                 ^\n  119 |   });\n  120 |\n  121 |   test('source contains _STATUS_CFG for client_status badges', async ({}) => {\n    at /var/www/360lm/tests/client.spec.js:118:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:40.726Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Zon-c10ce-count-fetch-for-zone-isHead-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 17,
                        "line": 118
                      }
                    },
                    {
                      "workerIndex": 10,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:118:17",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 17,
                          "line": 118
                        },
                        "snippet": "  116 |     expect(src).toContain('fullAcct.zone');\n  117 |     expect(src).toContain('fullAcct.is_head');\n> 118 |     expect(src).toContain('is_head===undefined');\n      |                 ^\n  119 |   });\n  120 |\n  121 |   test('source contains _STATUS_CFG for client_status badges', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 17,
                            "line": 118
                          },
                          "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\"is_head===undefined\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  116 |     expect(src).toContain('fullAcct.zone');\n  117 |     expect(src).toContain('fullAcct.is_head');\n> 118 |     expect(src).toContain('is_head===undefined');\n      |                 ^\n  119 |   });\n  120 |\n  121 |   test('source contains _STATUS_CFG for client_status badges', async ({}) => {\n    at /var/www/360lm/tests/client.spec.js:118:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-05-28T12:42:41.647Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Zon-c10ce-count-fetch-for-zone-isHead-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 17,
                        "line": 118
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "9bf021294cad394269b8-fa93aa2530e3667f91cd",
              "file": "client.spec.js",
              "line": 112,
              "column": 3
            },
            {
              "title": "source contains _STATUS_CFG for client_status badges",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 11,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 68,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:42.691Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-cde1a27bb291a4e9741a",
              "file": "client.spec.js",
              "line": 121,
              "column": 3
            },
            {
              "title": "enterRecce fetches by campaign_id not client_name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 11,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 19,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:42.810Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-8790c42dea96b25ff8dd",
              "file": "client.spec.js",
              "line": 131,
              "column": 3
            }
          ]
        },
        {
          "title": "Client Portal — Recce Approval Flow",
          "file": "client.spec.js",
          "line": 143,
          "column": 6,
          "specs": [
            {
              "title": "source contains enterRecceApproval and approval helpers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 4,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 41,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:25.346Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-845e854dc57bd868981c",
              "file": "client.spec.js",
              "line": 144,
              "column": 3
            },
            {
              "title": "source contains all three approval option handlers",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 4,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 46,
                      "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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:161:17",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 17,
                          "line": 161
                        },
                        "snippet": "  159 |     const resp = await ctx.get(BASE_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain(\"client_status:'approved'\");\n      |                 ^\n  162 |     expect(src).toContain(\"client_status:'rejected'\");\n  163 |     expect(src).toContain(\"client_status:'approved_with_changes'\");\n  164 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 17,
                            "line": 161
                          },
                          "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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  159 |     const resp = await ctx.get(BASE_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain(\"client_status:'approved'\");\n      |                 ^\n  162 |     expect(src).toContain(\"client_status:'rejected'\");\n  163 |     expect(src).toContain(\"client_status:'approved_with_changes'\");\n  164 |   });\n    at /var/www/360lm/tests/client.spec.js:161:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:25.439Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Rec-0205d-ee-approval-option-handlers-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 17,
                        "line": 161
                      }
                    },
                    {
                      "workerIndex": 5,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 152,
                      "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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:161:17",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 17,
                          "line": 161
                        },
                        "snippet": "  159 |     const resp = await ctx.get(BASE_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain(\"client_status:'approved'\");\n      |                 ^\n  162 |     expect(src).toContain(\"client_status:'rejected'\");\n  163 |     expect(src).toContain(\"client_status:'approved_with_changes'\");\n  164 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 17,
                            "line": 161
                          },
                          "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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  159 |     const resp = await ctx.get(BASE_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain(\"client_status:'approved'\");\n      |                 ^\n  162 |     expect(src).toContain(\"client_status:'rejected'\");\n  163 |     expect(src).toContain(\"client_status:'approved_with_changes'\");\n  164 |   });\n    at /var/www/360lm/tests/client.spec.js:161:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-05-28T12:42:26.899Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Rec-0205d-ee-approval-option-handlers-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 17,
                        "line": 161
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "9bf021294cad394269b8-7edfeac7d9e22b7138dd",
              "file": "client.spec.js",
              "line": 157,
              "column": 3
            },
            {
              "title": "source contains branding approval row insertion with change detection",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 123,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:28.707Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-c5b78a2f413438bff417",
              "file": "client.spec.js",
              "line": 166,
              "column": 3
            },
            {
              "title": "source contains fire-and-forget PDF call with TODO marker",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 40,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:28.892Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-875447fe0fd70199012c",
              "file": "client.spec.js",
              "line": 179,
              "column": 3
            },
            {
              "title": "approval screen loads (unauthenticated shows login, not crash)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 932,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:28.998Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-a712bad3c7c6d0702515",
              "file": "client.spec.js",
              "line": 187,
              "column": 3
            },
            {
              "title": "source contains loadBrandingApprovalResults for read-only view",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 42,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:30.207Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-d8c575235d0b02ff7fd9",
              "file": "client.spec.js",
              "line": 195,
              "column": 3
            },
            {
              "title": "source contains enterRecceApproval and approval helpers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 11,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 21,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:42.863Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-efda3571c55d1c8cb55c",
              "file": "client.spec.js",
              "line": 144,
              "column": 3
            },
            {
              "title": "source contains all three approval option handlers",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 11,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 39,
                      "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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:161:17",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 17,
                          "line": 161
                        },
                        "snippet": "  159 |     const resp = await ctx.get(BASE_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain(\"client_status:'approved'\");\n      |                 ^\n  162 |     expect(src).toContain(\"client_status:'rejected'\");\n  163 |     expect(src).toContain(\"client_status:'approved_with_changes'\");\n  164 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 17,
                            "line": 161
                          },
                          "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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  159 |     const resp = await ctx.get(BASE_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain(\"client_status:'approved'\");\n      |                 ^\n  162 |     expect(src).toContain(\"client_status:'rejected'\");\n  163 |     expect(src).toContain(\"client_status:'approved_with_changes'\");\n  164 |   });\n    at /var/www/360lm/tests/client.spec.js:161:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:42.915Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Rec-0205d-ee-approval-option-handlers-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 17,
                        "line": 161
                      }
                    },
                    {
                      "workerIndex": 12,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:161:17",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 17,
                          "line": 161
                        },
                        "snippet": "  159 |     const resp = await ctx.get(BASE_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain(\"client_status:'approved'\");\n      |                 ^\n  162 |     expect(src).toContain(\"client_status:'rejected'\");\n  163 |     expect(src).toContain(\"client_status:'approved_with_changes'\");\n  164 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 17,
                            "line": 161
                          },
                          "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\"client_status:'approved'\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<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,user-scalable=no\\\">\u001b[39m\n\u001b[31m<title>Client Portal · 360LM</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"/client/manifest.json\\\">\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0d0d0d\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0d0d0d;--surface:#1a1a1a;--surface2:#242424;\u001b[39m\n\u001b[31m  --border:#2a2a2a;--text:#f0f0f0;--muted:#888;\u001b[39m\n\u001b[31m  --primary:#6366f1;--primary-dim:rgba(99,102,241,.15);\u001b[39m\n\u001b[31m  --green:#22c55e;--red:#ef4444;--amber:#f59e0b;\u001b[39m\n\u001b[31m  --hot:#ef4444;--warm:#f59e0b;--cold:#6366f1;\u001b[39m\n\u001b[31m  --radius:12px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;overflow-x:hidden}·\u001b[39m\n\u001b[31m.screen{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.screen.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;flex-shrink:0}\u001b[39m\n\u001b[31m.hdr-title{flex:1;font-size:1rem;font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-sub{font-size:.72rem;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.back-btn{font-size:1.4rem;color:var(--text);background:none;border:none;cursor:pointer;padding:0 4px;flex-shrink:0}\u001b[39m\n\u001b[31m.icon-btn{background:none;border:none;cursor:pointer;color:var(--muted);font-size:1.1rem;padding:6px;flex-shrink:0}·\u001b[39m\n\u001b[31m.body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.login-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:24px;gap:18px}\u001b[39m\n\u001b[31m.login-logo{font-size:2.8rem}\u001b[39m\n\u001b[31m.login-title{font-size:1.4rem;font-weight:800;text-align:center}\u001b[39m\n\u001b[31m.login-sub{font-size:.82rem;color:var(--muted);text-align:center;margin-top:-10px}\u001b[39m\n\u001b[31m.login-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;width:100%;max-width:340px;display:flex;flex-direction:column;gap:14px}\u001b[39m\n\u001b[31m.lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31minput[type=text],input[type=password]{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31minput[type=text]:focus,input[type=password]:focus{border-color:var(--primary)}·\u001b[39m\n\u001b[31m.pin-dots{display:flex;gap:10px;justify-content:center;margin:2px 0 6px}\u001b[39m\n\u001b[31m.pin-dot{width:12px;height:12px;border-radius:50%;background:var(--border);transition:background .12s}\u001b[39m\n\u001b[31m.pin-dot.filled{background:var(--primary)}\u001b[39m\n\u001b[31m.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}\u001b[39m\n\u001b[31m.kp{background:var(--surface2);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:1.2rem;font-weight:600;padding:14px;cursor:pointer;transition:background .12s;text-align:center}\u001b[39m\n\u001b[31m.kp:active{background:#333}\u001b[39m\n\u001b[31m.kp.del{font-size:.85rem;color:var(--muted)}\u001b[39m\n\u001b[31m.kp.go{background:var(--primary);color:#fff;border-color:var(--primary)}\u001b[39m\n\u001b[31m.kp.go:disabled{opacity:.5;cursor:not-allowed}·\u001b[39m\n\u001b[31m.btn{background:var(--primary);color:#fff;border:none;border-radius:var(--radius);font-size:.95rem;font-weight:600;padding:13px;width:100%;cursor:pointer;transition:opacity .15s}\u001b[39m\n\u001b[31m.btn:disabled{opacity:.5;cursor:not-allowed}\u001b[39m\n\u001b[31m.btn-sec{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}\u001b[39m\n\u001b[31m.btn-danger{background:rgba(239,68,68,.08);color:var(--red);border:1px solid rgba(239,68,68,.25);border-radius:var(--radius);font-size:.9rem;font-weight:600;padding:11px;width:100%;cursor:pointer}·\u001b[39m\n\u001b[31m.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;cursor:pointer;transition:border-color .18s}\u001b[39m\n\u001b[31m.card:active,.card:hover{border-color:var(--primary)}\u001b[39m\n\u001b[31m.card-name{font-size:1rem;font-weight:700;margin-bottom:2px}\u001b[39m\n\u001b[31m.card-company{font-size:.82rem;color:var(--muted)}\u001b[39m\n\u001b[31m.card-meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:8px}·\u001b[39m\n\u001b[31m.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:20px;font-size:.7rem;font-weight:700}\u001b[39m\n\u001b[31m.b-hot{background:rgba(239,68,68,.14);color:var(--hot)}\u001b[39m\n\u001b[31m.b-warm{background:rgba(245,158,11,.14);color:var(--amber)}\u001b[39m\n\u001b[31m.b-cold{background:rgba(99,102,241,.14);color:var(--cold)}\u001b[39m\n\u001b[31m.b-green{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.b-muted{background:rgba(255,255,255,.06);color:var(--muted)}·\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:none}\u001b[39m\n\u001b[31m.filter-row::-webkit-scrollbar{display:none}\u001b[39m\n\u001b[31m.chip{background:var(--surface2);border:1px solid var(--border);border-radius:20px;color:var(--muted);font-size:.78rem;font-weight:600;padding:5px 11px;cursor:pointer;white-space:nowrap;transition:all .14s}\u001b[39m\n\u001b[31m.chip.active{background:var(--primary-dim);border-color:var(--primary);color:var(--primary)}·\u001b[39m\n\u001b[31m.sec-hdr{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;padding:2px 0 8px}·\u001b[39m\n\u001b[31m.tile-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}\u001b[39m\n\u001b[31m.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;transition:border-color .18s;text-align:center}\u001b[39m\n\u001b[31m.tile:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.tile-icon{font-size:2rem}\u001b[39m\n\u001b[31m.tile-lbl{font-size:.92rem;font-weight:700}\u001b[39m\n\u001b[31m.tile-cnt{font-size:.76rem;color:var(--muted)}·\u001b[39m\n\u001b[31m.acct-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:border-color .18s;margin-bottom:8px}\u001b[39m\n\u001b[31m.acct-row:active{border-color:var(--primary)}\u001b[39m\n\u001b[31m.avatar{width:38px;height:38px;border-radius:50%;background:var(--primary-dim);color:var(--primary);font-size:.95rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}\u001b[39m\n\u001b[31m.acct-info{flex:1;min-width:0}\u001b[39m\n\u001b[31m.acct-name{font-size:.93rem;font-weight:700}\u001b[39m\n\u001b[31m.acct-sub{font-size:.76rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.pill{font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.pill-on{background:rgba(34,197,94,.14);color:var(--green)}\u001b[39m\n\u001b[31m.pill-off{background:rgba(239,68,68,.1);color:var(--red)}·\u001b[39m\n\u001b[31m.grant-row{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--surface2);border-radius:8px;margin-bottom:6px}\u001b[39m\n\u001b[31m.grant-type{font-size:.67rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);background:var(--primary-dim);padding:2px 7px;border-radius:20px;flex-shrink:0}\u001b[39m\n\u001b[31m.grant-lbl{flex:1;font-size:.86rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.grant-del{background:none;border:none;cursor:pointer;color:var(--red);font-size:.9rem;padding:4px;flex-shrink:0}·\u001b[39m\n\u001b[31m.info-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;display:flex;gap:20px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.info-item{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.info-lbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase}\u001b[39m\n\u001b[31m.info-val{font-size:.88rem;font-weight:600}·\u001b[39m\n\u001b[31m.pres-hero{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-name{font-size:1.25rem;font-weight:800;margin-bottom:3px}\u001b[39m\n\u001b[31m.pres-company{font-size:.9rem;color:var(--muted);margin-bottom:10px}\u001b[39m\n\u001b[31m.pres-badges{display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.pres-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-section h3{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px}\u001b[39m\n\u001b[31m.pres-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}\u001b[39m\n\u001b[31m.pres-field{display:flex;flex-direction:column;gap:2px}\u001b[39m\n\u001b[31m.pres-field.wide{grid-column:span 2}\u001b[39m\n\u001b[31m.pres-flbl{font-size:.68rem;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}\u001b[39m\n\u001b[31m.pres-fval{font-size:.88rem;font-weight:500}\u001b[39m\n\u001b[31m.photo-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px}\u001b[39m\n\u001b[31m.photo-cell{border-radius:8px;overflow:hidden;aspect-ratio:4/3;background:var(--surface2);cursor:pointer}\u001b[39m\n\u001b[31m.photo-cell img{width:100%;height:100%;object-fit:cover}\u001b[39m\n\u001b[31m.photo-cell.full{grid-column:span 2}·\u001b[39m\n\u001b[31m.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:400;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.open{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;width:100%;max-width:480px;padding:20px;display:flex;flex-direction:column;gap:13px;max-height:88vh;overflow-y:auto}\u001b[39m\n\u001b[31m.modal-title{font-size:.98rem;font-weight:700}·\u001b[39m\n\u001b[31m#lightbox{display:none;position:fixed;inset:0;background:#000;z-index:600;flex-direction:column;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lightbox.open{display:flex}\u001b[39m\n\u001b[31m#lightbox img{max-width:100%;max-height:90vh;object-fit:contain}\u001b[39m\n\u001b[31m#lb-close{position:absolute;top:14px;right:14px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:1rem;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev,#lb-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.12);border:none;color:#fff;font-size:1.2rem;border-radius:50%;width:38px;height:38px;cursor:pointer;display:flex;align-items:center;justify-content:center}\u001b[39m\n\u001b[31m#lb-prev{left:10px}#lb-next{right:10px}·\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:#222;color:#fff;padding:9px 18px;border-radius:20px;font-size:.86rem;z-index:999;transition:transform .28s;pointer-events:none;white-space:nowrap}\u001b[39m\n\u001b[31m#toast.show{transform:translateX(-50%) translateY(0)}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d}#toast.err{background:#7f1d1d}·\u001b[39m\n\u001b[31m.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--muted);text-align:center}\u001b[39m\n\u001b[31m.empty-ico{font-size:2.2rem}\u001b[39m\n\u001b[31m.divider{height:1px;background:var(--border)}\u001b[39m\n\u001b[31m.staff-link{text-align:center;font-size:.78rem;color:var(--muted);cursor:pointer;text-decoration:underline}\u001b[39m\n\u001b[31mselect{background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%}\u001b[39m\n\u001b[31m.login-tab-bar{display:flex;width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}\u001b[39m\n\u001b[31m.login-tab-btn{flex:1;padding:10px;text-align:center;font-size:.85rem;font-weight:600;background:none;border:none;color:var(--muted);cursor:pointer;transition:.15s}\u001b[39m\n\u001b[31m.login-tab-btn.active{background:var(--primary);color:#fff}\u001b[39m\n\u001b[31m.cc-wrap{width:100%;max-width:340px;display:flex;flex-direction:column;gap:12px}\u001b[39m\n\u001b[31m.cc-lbl{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}\u001b[39m\n\u001b[31m.cc-err{color:var(--red);font-size:.82rem;text-align:center;min-height:18px}\u001b[39m\n\u001b[31m.pin-dot.optional{opacity:.28}·\u001b[39m\n\u001b[31m/* ── Approval screen ── */\u001b[39m\n\u001b[31m.appr-banner{background:linear-gradient(135deg,var(--surface2),var(--surface));border:1px solid var(--border);border-radius:var(--radius);padding:28px 20px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-banner-name{font-size:1.35rem;font-weight:800;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-meta{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}\u001b[39m\n\u001b[31m.appr-banner-client{font-size:.8rem;color:var(--muted);margin-top:4px}\u001b[39m\n\u001b[31m.appr-banner-campaign{font-size:.76rem;color:var(--muted);font-family:monospace;word-break:break-all}\u001b[39m\n\u001b[31m.appr-opts{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}\u001b[39m\n\u001b[31m.appr-opt-btn{width:100%;padding:14px 16px;border:1.5px solid var(--border);border-radius:10px;background:var(--surface2);color:var(--text);font-size:.9rem;font-weight:600;cursor:pointer;text-align:left;transition:all .14s}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-approve{background:rgba(34,197,94,.14);border-color:var(--green);color:var(--green)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-reject{background:rgba(239,68,68,.14);border-color:var(--red);color:var(--red)}\u001b[39m\n\u001b[31m.appr-opt-btn.active.appr-opt-comment{background:rgba(245,158,11,.14);border-color:var(--amber);color:var(--amber)}\u001b[39m\n\u001b[31m.appr-br-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:10px;overflow:hidden}\u001b[39m\n\u001b[31m.appr-br-title{font-size:.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;padding:8px 12px 0}\u001b[39m\n\u001b[31m.appr-br-inner{display:grid;grid-template-columns:72px 1fr;gap:10px;padding:8px 12px}\u001b[39m\n\u001b[31m.appr-br-photo{width:72px;height:54px;object-fit:cover;border-radius:6px;display:block}\u001b[39m\n\u001b[31m.appr-br-photo-ph{width:72px;height:54px;background:var(--surface2);border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--muted)}\u001b[39m\n\u001b[31m.appr-br-specs{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.appr-br-col{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.appr-br-spec-row{display:flex;justify-content:space-between;font-size:.78rem;padding:2px 0;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.appr-br-lbl{color:var(--muted);font-size:.72rem}\u001b[39m\n\u001b[31m.appr-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:12px}·\u001b[39m\n\u001b[31m/* ── Toggle (is_head) ── */\u001b[39m\n\u001b[31m.toggle-wrap{display:flex;align-items:center;justify-content:space-between;padding:8px 0}\u001b[39m\n\u001b[31m.toggle-lbl{font-size:.88rem;font-weight:600}\u001b[39m\n\u001b[31m.toggle-sub{font-size:.74rem;color:var(--muted);margin-top:1px}\u001b[39m\n\u001b[31m.tgl{position:relative;width:42px;height:24px;flex-shrink:0}\u001b[39m\n\u001b[31m.tgl input{opacity:0;width:0;height:0;position:absolute}\u001b[39m\n\u001b[31m.tgl-sl{position:absolute;inset:0;background:var(--border);border-radius:12px;cursor:pointer;transition:.2s}\u001b[39m\n\u001b[31m.tgl-sl:before{content:'';position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}\u001b[39m\n\u001b[31minput:checked+.tgl-sl{background:var(--primary)}\u001b[39m\n\u001b[31minput:checked+.tgl-sl:before{transform:translateX(18px)}·\u001b[39m\n\u001b[31m/* ── Zone / is_head display chips ── */\u001b[39m\n\u001b[31m.zone-chip{display:inline-flex;align-items:center;gap:4px;background:var(--primary-dim);border:1px solid var(--primary);color:var(--primary);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m.head-chip{display:inline-flex;align-items:center;gap:4px;background:rgba(245,158,11,.14);border:1px solid var(--amber);color:var(--amber);border-radius:12px;padding:2px 9px;font-size:.74rem;font-weight:700}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── Login ────────────────────────────────────── -->\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\\\">👤</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-title\\\">Client Portal</div>\u001b[39m\n\u001b[31m    <div class=\\\"login-sub\\\">360 Degree Marketing</div>\u001b[39m\n\u001b[31m    <!-- Login mode tabs -->\u001b[39m\n\u001b[31m    <div class=\\\"login-tab-bar\\\">\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn active\\\" id=\\\"tab-pin\\\" onclick=\\\"setClientLoginMode('pin')\\\">Client ID</button>\u001b[39m\n\u001b[31m      <button class=\\\"login-tab-btn\\\" id=\\\"tab-code\\\" onclick=\\\"setClientLoginMode('code')\\\">Company Code</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- PIN tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-pin-tab\\\" class=\\\"login-card\\\">\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">Client ID</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"login-id\\\" placeholder=\\\"Enter your ID\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\"\u001b[39m\n\u001b[31m               oninput=\\\"this.value=this.value.toLowerCase().replace(/\\\\s/g,'');resetPin()\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div>\u001b[39m\n\u001b[31m        <div class=\\\"lbl\\\">PIN</div>\u001b[39m\n\u001b[31m        <div class=\\\"pin-dots\\\">\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d0\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d1\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d2\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d3\\\"></div>\u001b[39m\n\u001b[31m          <div class=\\\"pin-dot\\\" id=\\\"d4\\\"></div><div class=\\\"pin-dot\\\" id=\\\"d5\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"keypad\\\">\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('1')\\\">1</button><button class=\\\"kp\\\" onclick=\\\"kp('2')\\\">2</button><button class=\\\"kp\\\" onclick=\\\"kp('3')\\\">3</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('4')\\\">4</button><button class=\\\"kp\\\" onclick=\\\"kp('5')\\\">5</button><button class=\\\"kp\\\" onclick=\\\"kp('6')\\\">6</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp\\\" onclick=\\\"kp('7')\\\">7</button><button class=\\\"kp\\\" onclick=\\\"kp('8')\\\">8</button><button class=\\\"kp\\\" onclick=\\\"kp('9')\\\">9</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp del\\\" onclick=\\\"kpDel()\\\">⌫</button><button class=\\\"kp\\\" onclick=\\\"kp('0')\\\">0</button>\u001b[39m\n\u001b[31m          <button class=\\\"kp go\\\" id=\\\"kp-go\\\" onclick=\\\"doClientLogin()\\\" disabled>→</button>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <!-- Company Code tab -->\u001b[39m\n\u001b[31m    <div id=\\\"cl-code-tab\\\" class=\\\"cc-wrap\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m      <!-- Step 1: Select Company -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step1\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Company</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-company-sel\\\" onchange=\\\"onCCCompanySelect()\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select company —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 2: Company Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step2\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Company Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-company-code\\\" placeholder=\\\"Enter company code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"none\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') verifyCCCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"verifyCCCode()\\\">Verify →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <!-- Step 3: Person + Code -->\u001b[39m\n\u001b[31m      <div id=\\\"cc-step3\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\">Select Your Name</div>\u001b[39m\n\u001b[31m        <select id=\\\"cc-person-sel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— select your name —</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div class=\\\"cc-lbl\\\" style=\\\"margin-top:10px\\\">Your Access Code</div>\u001b[39m\n\u001b[31m        <input type=\\\"text\\\" id=\\\"cc-ind-code\\\" placeholder=\\\"Enter your code\\\" maxlength=\\\"20\\\"\u001b[39m\n\u001b[31m               autocomplete=\\\"off\\\" autocapitalize=\\\"characters\\\"\u001b[39m\n\u001b[31m               onkeydown=\\\"if(event.key==='Enter') loginWithCode()\\\"\u001b[39m\n\u001b[31m               style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:1rem;padding:10px 12px;outline:none;width:100%;letter-spacing:2px\\\">\u001b[39m\n\u001b[31m        <button type=\\\"button\\\" class=\\\"btn\\\" style=\\\"margin-top:10px\\\" onclick=\\\"loginWithCode()\\\">Continue →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cc-err\\\" id=\\\"cc-err\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"staff-link\\\" onclick=\\\"openModal('modal-staff-login')\\\">⚙ Staff Access</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Admin Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-admin\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"goHub()\\\">⌂</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Client Portal</div><div class=\\\"hdr-sub\\\" id=\\\"admin-who\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\">Client Accounts</div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn\\\" onclick=\\\"openNewAcct()\\\">+ New Client Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Account Detail ────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-acct\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-admin');loadAdmin()\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"acct-hdr-name\\\">Account</div><div class=\\\"hdr-sub\\\" id=\\\"acct-hdr-id\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"acct-info-box\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"acct-zone-row\\\" style=\\\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-hdr\\\" style=\\\"margin-top:4px\\\">Access Grants</div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-list\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openAddGrant()\\\">+ Add Access Grant</button>\u001b[39m\n\u001b[31m    <div class=\\\"divider\\\"></div>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openEditZoneHead()\\\">🗺 Edit Zone / Head Status</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-sec\\\" onclick=\\\"openResetPin()\\\">🔑 Reset PIN</button>\u001b[39m\n\u001b[31m    <button class=\\\"btn-danger\\\" id=\\\"toggle-acct-btn\\\" onclick=\\\"toggleAccount()\\\">⛔ Deactivate Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Client Home ────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-home\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"home-name\\\">Portal</div><div class=\\\"hdr-sub\\\" id=\\\"home-welcome\\\"></div></div>\u001b[39m\n\u001b[31m    <button class=\\\"icon-btn\\\" onclick=\\\"signOut()\\\" title=\\\"Sign out\\\">⎋</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\"><div class=\\\"tile-grid\\\" id=\\\"home-tiles\\\"></div></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Responses ──────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-responses\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Activity Responses</div><div class=\\\"hdr-sub\\\" id=\\\"resp-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"resp-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"resp-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce View ─────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-home')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\">Branding Recce</div><div class=\\\"hdr-sub\\\" id=\\\"recce-sub\\\">Loading…</div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div id=\\\"recce-filters\\\" style=\\\"display:flex;flex-direction:column;gap:8px\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"recce-list\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Recce Approval ─────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-recce-approval\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-recce')\\\">‹</button>\u001b[39m\n\u001b[31m    <div style=\\\"flex:1;min-width:0\\\"><div class=\\\"hdr-title\\\" id=\\\"approval-hdr-title\\\">Recce Review</div><div class=\\\"hdr-sub\\\" id=\\\"approval-hdr-sub\\\"></div></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"approval-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Presentation ───────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"screen-pres\\\" class=\\\"screen\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"show('screen-responses')\\\">‹</button>\u001b[39m\n\u001b[31m    <div class=\\\"hdr-title\\\">Response Detail</div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" id=\\\"pres-body\\\"></div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Staff Login ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-staff-login\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Staff Login</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Employee ID</div><input type=\\\"text\\\" id=\\\"sl-id\\\" placeholder=\\\"Enter employee ID\\\" autocomplete=\\\"off\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN</div><input type=\\\"password\\\" id=\\\"sl-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doStaffLogin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-staff-login')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"sl-btn\\\" onclick=\\\"doStaffLogin()\\\">Sign In</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: New Account ─────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-new-acct\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">New Client Account</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Login ID</div><input type=\\\"text\\\" id=\\\"na-id\\\" placeholder=\\\"e.g. salman\\\" autocomplete=\\\"off\\\" spellcheck=\\\"false\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Company / Display Name</div><input type=\\\"text\\\" id=\\\"na-display\\\" placeholder=\\\"e.g. Epson India Ltd\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">Contact Person Name</div><input type=\\\"text\\\" id=\\\"na-contact\\\" placeholder=\\\"e.g. Salman\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"na-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') createAccount()\\\"></div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone (geography)</div>\u001b[39m\n\u001b[31m      <select id=\\\"na-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone (or set later) —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns granted to this account</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"na-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-new-acct')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"na-btn\\\" onclick=\\\"createAccount()\\\">Create Account</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Add Grant ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-add-grant\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Add Access Grant</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Resource Type</div>\u001b[39m\n\u001b[31m      <select id=\\\"grant-type\\\" onchange=\\\"loadGrantResources()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"activity\\\">📋 Activity</option>\u001b[39m\n\u001b[31m        <option value=\\\"campaign\\\">📍 Campaign (Recce access)</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce\\\">🗃 Recce — legacy client_name</option>\u001b[39m\n\u001b[31m        <option value=\\\"installation\\\" disabled>🏗️ Installation (Phase 3)</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"grant-res-wrap\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-add-grant')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"grant-btn\\\" onclick=\\\"doAddGrant()\\\">Add Grant</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Edit Zone / Head Status ────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-edit-zone\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Edit Zone / Head Status</div>\u001b[39m\n\u001b[31m    <div>\u001b[39m\n\u001b[31m      <div class=\\\"lbl\\\">Zone</div>\u001b[39m\n\u001b[31m      <select id=\\\"ez-zone\\\" style=\\\"background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;width:100%\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— No zone —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"toggle-wrap\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <div><div class=\\\"toggle-lbl\\\">Head of Client</div><div class=\\\"toggle-sub\\\">Bypasses zone filter — sees all campaigns</div></div>\u001b[39m\n\u001b[31m      <label class=\\\"tgl\\\"><input type=\\\"checkbox\\\" id=\\\"ez-ishead\\\"><span class=\\\"tgl-sl\\\"></span></label>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-edit-zone')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"ez-btn\\\" onclick=\\\"saveZoneHead()\\\">Save</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Modal: Reset PIN ───────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"modal-reset-pin\\\" class=\\\"modal\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-title\\\">Reset Client PIN</div>\u001b[39m\n\u001b[31m    <div><div class=\\\"lbl\\\">New PIN (4–6 digits)</div><input type=\\\"password\\\" id=\\\"rp-pin\\\" placeholder=\\\"4–6 digit PIN\\\" inputmode=\\\"numeric\\\" minlength=\\\"4\\\" maxlength=\\\"6\\\" onkeydown=\\\"if(event.key==='Enter') doResetPin()\\\"></div>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn-sec\\\" onclick=\\\"closeModal('modal-reset-pin')\\\">Cancel</button>\u001b[39m\n\u001b[31m    <button type=\\\"button\\\" class=\\\"btn\\\" id=\\\"rp-btn\\\" onclick=\\\"doResetPin()\\\">Set New PIN</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── Lightbox ───────────────────────────────────── -->\u001b[39m\n\u001b[31m<div id=\\\"lightbox\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-close\\\" onclick=\\\"closeLb()\\\">✕</button>\u001b[39m\n\u001b[31m  <button id=\\\"lb-prev\\\" onclick=\\\"lbNav(-1)\\\">‹</button>\u001b[39m\n\u001b[31m  <img id=\\\"lb-img\\\" src=\\\"\\\" alt=\\\"\\\">\u001b[39m\n\u001b[31m  <button id=\\\"lb-next\\\" onclick=\\\"lbNav(1)\\\">›</button>\u001b[39m\n\u001b[31m</div>\u001b[39m\n\u001b[31m<div id=\\\"toast\\\" role=\\\"alert\\\" aria-live=\\\"assertive\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';\u001b[39m\n\u001b[31mconst API       = '/db';\u001b[39m\n\u001b[31mconst ADMIN_IDS = ['harish','pramod'];\u001b[39m\n\u001b[31mconst CL_H   = {'Content-Type':'application/json','Accept-Profile':'client','Content-Profile':'client','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CL_RD  = {'Content-Type':'application/json','Accept-Profile':'client'};\u001b[39m\n\u001b[31mconst ACT_RD = {'Content-Type':'application/json','Accept-Profile':'activity'};\u001b[39m\n\u001b[31mconst RC_RD  = {'Content-Type':'application/json','Accept-Profile':'recce'};\u001b[39m\n\u001b[31mconst RC_WR  = {'Content-Type':'application/json','Accept-Profile':'recce','Content-Profile':'recce','Prefer':'return=minimal'};\u001b[39m\n\u001b[31mconst INST_RD= {'Content-Type':'application/json','Accept-Profile':'installation'};\u001b[39m\n\u001b[31mconst EXP_H  = {'Content-Type':'application/json','Accept-Profile':'expense','Content-Profile':'expense','Prefer':'return=representation'};\u001b[39m\n\u001b[31mconst CLIENT_KEY = 'lm360-client-session';\u001b[39m\n\u001b[31mconst TTL_30D    = 30*24*60*60*1000;·\u001b[39m\n\u001b[31mlet adminSession  = null;\u001b[39m\n\u001b[31mlet clientSession = null;\u001b[39m\n\u001b[31mlet currentAcctId = null;\u001b[39m\n\u001b[31mlet currentGrants = [];\u001b[39m\n\u001b[31mlet _clientGrants = {activity:[], recce:[], campaign:[]};\u001b[39m\n\u001b[31mlet _accessibleCampaignIds = [];\u001b[39m\n\u001b[31mlet _recceRows = [], _recceDateF = new Set(), _recceBrandF = new Set();\u001b[39m\n\u001b[31mlet _approvalSub = null, _approvalOption = null, _brandingApprovalData = [];\u001b[39m\n\u001b[31mlet allResponses  = [];\u001b[39m\n\u001b[31mlet filteredResp  = [];\u001b[39m\n\u001b[31mlet actCache      = {};\u001b[39m\n\u001b[31mlet lbPhotos = [], lbIdx = 0;\u001b[39m\n\u001b[31mlet pinStr = '';\u001b[39m\n\u001b[31mlet loginAttempts=0, staffLoginAttempts=0;\u001b[39m\n\u001b[31mlet _areaF = new Set(), _dateF = new Set(), _leadF = new Set();\u001b[39m\n\u001b[31mlet ccCompanies = [], ccCompanyId = null, ccPersons = [];·\u001b[39m\n\u001b[31m/* ── util ── */\u001b[39m\n\u001b[31mfunction esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\\\"/g,'&quot;') }\u001b[39m\n\u001b[31mlet _tt;\u001b[39m\n\u001b[31mfunction toast(m,t='ok'){ const el=document.getElementById('toast'); el.textContent=m; el.className='show '+t; clearTimeout(_tt); _tt=setTimeout(()=>el.className='',2600); }\u001b[39m\n\u001b[31mfunction show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById(id).classList.add('active'); }\u001b[39m\n\u001b[31mfunction openModal(id){ document.getElementById(id).classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeModal(id){ document.getElementById(id).classList.remove('open'); }\u001b[39m\n\u001b[31mfunction goHub(){ window.location.href='/hub/'; }\u001b[39m\n\u001b[31masync function fetchJ(url,opts={}){ const r=await fetch(url,opts); if(!r.ok) throw new Error(`Server error (${r.status})`); return r.json(); }·\u001b[39m\n\u001b[31m/* ── PIN ── */\u001b[39m\n\u001b[31mfunction kp(d){ if(pinStr.length>=6) return; pinStr+=d; renderDots(); if(pinStr.length===6) doClientLogin(); }\u001b[39m\n\u001b[31mfunction kpDel(){ pinStr=pinStr.slice(0,-1); renderDots(); }\u001b[39m\n\u001b[31mfunction renderDots(){\u001b[39m\n\u001b[31m  for(let i=0;i<6;i++){\u001b[39m\n\u001b[31m    const dot=document.getElementById('d'+i);\u001b[39m\n\u001b[31m    dot.classList.toggle('filled', i<pinStr.length);\u001b[39m\n\u001b[31m    dot.classList.toggle('optional', pinStr.length>=4 && i>=pinStr.length);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const goBtn=document.getElementById('kp-go');\u001b[39m\n\u001b[31m  if(goBtn) goBtn.disabled = pinStr.length<4;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction resetPin(){ pinStr=''; renderDots(); }·\u001b[39m\n\u001b[31m/* ── Client login ── */\u001b[39m\n\u001b[31masync function doClientLogin(){\u001b[39m\n\u001b[31m  const id = document.getElementById('login-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!id || pinStr.length<4){ toast(id ? 'Enter your PIN' : 'Enter ID and PIN','err'); return; }\u001b[39m\n\u001b[31m  if(loginAttempts>=5){ toast('Too many attempts. Please wait.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('kp-go');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_pin:pinStr})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const acct = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!acct){ loginAttempts++; toast('Invalid ID or PIN','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    if(!acct.active){ toast('Account inactive. Contact admin.','err'); resetPin(); return; }\u001b[39m\n\u001b[31m    loginAttempts=0;\u001b[39m\n\u001b[31m    // Separate fetch for zone/isHead — RPC may not return new columns\u001b[39m\n\u001b[31m    let fullAcct = acct;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const fa = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(acct.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m      if(fa&&fa[0]) fullAcct = fa[0];\u001b[39m\n\u001b[31m    } catch(_){}\u001b[39m\n\u001b[31m    clientSession={\u001b[39m\n\u001b[31m      id:acct.id,\u001b[39m\n\u001b[31m      displayName:acct.display_name,\u001b[39m\n\u001b[31m      contactName:acct.contact_name,\u001b[39m\n\u001b[31m      zone:    fullAcct.zone    ?? null,\u001b[39m\n\u001b[31m      isHead:  fullAcct.is_head ?? false,\u001b[39m\n\u001b[31m      loginAt: Date.now()\u001b[39m\n\u001b[31m    };\u001b[39m\n\u001b[31m    localStorage.setItem(CLIENT_KEY,JSON.stringify(clientSession));\u001b[39m\n\u001b[31m    const _next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m    if(_next&&_next.startsWith('/')){ window.location.href=_next; return; }\u001b[39m\n\u001b[31m    await enterClientHome();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed. Try again.','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='→'; resetPin(); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Staff login (admin fallback when not from Hub) ── */\u001b[39m\n\u001b[31masync function doStaffLogin(){\u001b[39m\n\u001b[31m  const id  = document.getElementById('sl-id').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const pin = document.getElementById('sl-pin').value.trim();\u001b[39m\n\u001b[31m  if(!ADMIN_IDS.includes(id)){ toast('Not an admin account','err'); return; }\u001b[39m\n\u001b[31m  if(!pin){ toast('Enter PIN','err'); return; }\u001b[39m\n\u001b[31m  if(staffLoginAttempts>=5){ toast('Too many attempts. Please wait.','err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('sl-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Checking…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${API}/rpc/verify_pin`,{method:'POST',headers:EXP_H,body:JSON.stringify({p_id:id,p_pin:pin})});\u001b[39m\n\u001b[31m    const data = await r.json();\u001b[39m\n\u001b[31m    const emp = Array.isArray(data)?data[0]:data;\u001b[39m\n\u001b[31m    if(!r.ok||!emp){ staffLoginAttempts++; toast('Invalid PIN','err'); return; }\u001b[39m\n\u001b[31m    staffLoginAttempts=0;\u001b[39m\n\u001b[31m    adminSession={id:emp.id,name:emp.name};\u001b[39m\n\u001b[31m    closeModal('modal-staff-login');\u001b[39m\n\u001b[31m    enterAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Login failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Sign In'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction signOut(){\u001b[39m\n\u001b[31m  clientSession=null; adminSession=null;\u001b[39m\n\u001b[31m  _clientGrants={activity:[],recce:[],campaign:[]};\u001b[39m\n\u001b[31m  _accessibleCampaignIds=[];\u001b[39m\n\u001b[31m  _approvalSub=null; _approvalOption=null; _brandingApprovalData=[];\u001b[39m\n\u001b[31m  localStorage.removeItem(CLIENT_KEY);\u001b[39m\n\u001b[31m  document.getElementById('login-id').value='';\u001b[39m\n\u001b[31m  resetPin();\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Session restore on load ── */\u001b[39m\n\u001b[31m(function restoreSession(){\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hub = JSON.parse(localStorage.getItem('lm360-session')||'null');\u001b[39m\n\u001b[31m    if(hub&&hub.empId&&ADMIN_IDS.includes(hub.empId)&&(Date.now()-new Date(hub.loginAt).getTime()<=12*3600*1000)){\u001b[39m\n\u001b[31m      adminSession={id:hub.empId,name:hub.name};\u001b[39m\n\u001b[31m      enterAdmin(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const cs = JSON.parse(localStorage.getItem(CLIENT_KEY)||'null');\u001b[39m\n\u001b[31m    if(cs&&cs.loginAt&&(Date.now()-cs.loginAt<TTL_30D)){\u001b[39m\n\u001b[31m      // Back-compat: older sessions lack zone/isHead\u001b[39m\n\u001b[31m      if(cs.zone===undefined)   cs.zone   = null;\u001b[39m\n\u001b[31m      if(cs.isHead===undefined) cs.isHead = false;\u001b[39m\n\u001b[31m      clientSession=cs;\u001b[39m\n\u001b[31m      enterClientHome(); return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e){}\u001b[39m\n\u001b[31m  show('screen-login');\u001b[39m\n\u001b[31m})();·\u001b[39m\n\u001b[31m/* ── Admin: account list ── */\u001b[39m\n\u001b[31mfunction enterAdmin(){\u001b[39m\n\u001b[31m  document.getElementById('admin-who').textContent=`Admin · ${adminSession.name||adminSession.id}`;\u001b[39m\n\u001b[31m  show('screen-admin');\u001b[39m\n\u001b[31m  loadAdmin();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAdmin(){\u001b[39m\n\u001b[31m  const el=document.getElementById('acct-list');\u001b[39m\n\u001b[31m  el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const list = await fetchJ(`${API}/accounts?order=created_at.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!list.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">👤</div><p>No client accounts yet</p></div>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=list.map(a=>`\u001b[39m\n\u001b[31m      <div class=\\\"acct-row\\\" onclick=\\\"openAccount('${esc(a.id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"avatar\\\">${esc((a.display_name||a.id)[0].toUpperCase())}</div>\u001b[39m\n\u001b[31m        <div class=\\\"acct-info\\\">\u001b[39m\n\u001b[31m          <div class=\\\"acct-name\\\">${esc(a.display_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"acct-sub\\\">${esc(a.id)}${a.contact_name?' · '+esc(a.contact_name):''}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Off'}</span>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Admin: account detail ── */\u001b[39m\n\u001b[31masync function openAccount(id){\u001b[39m\n\u001b[31m  currentAcctId=id;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [a] = await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-name').textContent=a.display_name;\u001b[39m\n\u001b[31m    document.getElementById('acct-hdr-id').textContent=a.id;\u001b[39m\n\u001b[31m    document.getElementById('acct-info-box').innerHTML=`\u001b[39m\n\u001b[31m      <div class=\\\"info-box\\\">\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Company</div><div class=\\\"info-val\\\">${esc(a.display_name)}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Contact</div><div class=\\\"info-val\\\">${esc(a.contact_name||'—')}</div></div>\u001b[39m\n\u001b[31m        <div class=\\\"info-item\\\"><div class=\\\"info-lbl\\\">Status</div><div class=\\\"info-val\\\"><span class=\\\"pill ${a.active?'pill-on':'pill-off'}\\\">${a.active?'Active':'Inactive'}</span></div></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    // Zone + is_head row\u001b[39m\n\u001b[31m    const zoneRowEl=document.getElementById('acct-zone-row');\u001b[39m\n\u001b[31m    if(zoneRowEl){\u001b[39m\n\u001b[31m      zoneRowEl.innerHTML=[\u001b[39m\n\u001b[31m        a.zone?`<span class=\\\"zone-chip\\\">🗺 ${esc(a.zone)}</span>`:'',\u001b[39m\n\u001b[31m        a.is_head?`<span class=\\\"head-chip\\\">👑 Head of Client</span>`:'',\u001b[39m\n\u001b[31m      ].filter(Boolean).join('')||`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">No zone set</span>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m    btn.textContent=a.active?'⛔ Deactivate Account':'✅ Reactivate Account';\u001b[39m\n\u001b[31m    btn.className=a.active?'btn-danger':'btn-sec';\u001b[39m\n\u001b[31m    btn._isActive=a.active;\u001b[39m\n\u001b[31m    await loadGrants(id);\u001b[39m\n\u001b[31m    show('screen-acct');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed to load account','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadGrants(accountId){\u001b[39m\n\u001b[31m  const el=document.getElementById('grant-list');\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    currentGrants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(accountId)}&order=id.asc`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(!currentGrants.length){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted);padding:4px 0 8px\\\">No grants yet — add one below.</p>`; return; }\u001b[39m\n\u001b[31m    el.innerHTML=currentGrants.map(g=>`\u001b[39m\n\u001b[31m      <div class=\\\"grant-row\\\">\u001b[39m\n\u001b[31m        <span class=\\\"grant-type\\\">${esc(g.resource_type)}</span>\u001b[39m\n\u001b[31m        <span class=\\\"grant-lbl\\\">${esc(g.label||g.resource_id)}</span>\u001b[39m\n\u001b[31m        <button class=\\\"grant-del\\\" onclick=\\\"removeGrant(${g.id})\\\" title=\\\"Remove\\\">✕</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m  } catch(e){ el.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load grants</p>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleAccount(){\u001b[39m\n\u001b[31m  const btn=document.getElementById('toggle-acct-btn');\u001b[39m\n\u001b[31m  const makeActive=!btn._isActive;\u001b[39m\n\u001b[31m  btn.disabled=true;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({active:makeActive})});\u001b[39m\n\u001b[31m    toast(makeActive?'Account activated':'Account deactivated','ok');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Update failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── New account ── */\u001b[39m\n\u001b[31masync function openNewAcct(){\u001b[39m\n\u001b[31m  ['na-id','na-display','na-contact','na-pin'].forEach(i=>document.getElementById(i).value='');\u001b[39m\n\u001b[31m  document.getElementById('na-ishead').checked=false;\u001b[39m\n\u001b[31m  // Load zones into dropdown\u001b[39m\n\u001b[31m  const sel=document.getElementById('na-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-new-acct');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function createAccount(){\u001b[39m\n\u001b[31m  const idEl=document.getElementById('na-id');\u001b[39m\n\u001b[31m  const displayEl=document.getElementById('na-display');\u001b[39m\n\u001b[31m  const contactEl=document.getElementById('na-contact');\u001b[39m\n\u001b[31m  const pinEl=document.getElementById('na-pin');\u001b[39m\n\u001b[31m  const id=idEl.value.trim().toLowerCase();\u001b[39m\n\u001b[31m  const display=displayEl.value.trim();\u001b[39m\n\u001b[31m  const contact=contactEl.value.trim();\u001b[39m\n\u001b[31m  const pin=pinEl.value.trim();\u001b[39m\n\u001b[31m  const zone=document.getElementById('na-zone')?.value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('na-ishead')?.checked||false;\u001b[39m\n\u001b[31m  [idEl,displayEl,contactEl,pinEl].forEach(el=>el.style.borderColor='');\u001b[39m\n\u001b[31m  let invalid=false;\u001b[39m\n\u001b[31m  if(!id){idEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!display){displayEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){pinEl.style.borderColor='var(--red)';invalid=true;}\u001b[39m\n\u001b[31m  if(invalid){toast('Fill all fields (PIN min 4 digits)','err');return;}\u001b[39m\n\u001b[31m  const btn=document.getElementById('na-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Creating…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/create_client_account`,{method:'POST',headers:CL_H,body:JSON.stringify({p_id:id,p_display_name:display,p_contact_name:contact,p_pin:pin})});\u001b[39m\n\u001b[31m    // After account created, patch zone + is_head if set\u001b[39m\n\u001b[31m    if(r.ok&&(zone||isHead)){\u001b[39m\n\u001b[31m      fetch(`${API}/accounts?id=eq.${encodeURIComponent(id)}`,{method:'PATCH',headers:CL_H,body:JSON.stringify({zone:zone||null,is_head:isHead})}).catch(()=>{});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')||t.includes('duplicate')?'ID already exists':'Create failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Account created!','ok');\u001b[39m\n\u001b[31m    closeModal('modal-new-acct');\u001b[39m\n\u001b[31m    loadAdmin();\u001b[39m\n\u001b[31m  } catch(e){ toast('Create failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Create Account'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Add grant ── */\u001b[39m\n\u001b[31masync function openAddGrant(){ document.getElementById('grant-type').value='activity'; await loadGrantResources(); openModal('modal-add-grant'); }\u001b[39m\n\u001b[31masync function loadGrantResources(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const wrap=document.getElementById('grant-res-wrap');\u001b[39m\n\u001b[31m  document.getElementById('grant-btn').disabled=false;\u001b[39m\n\u001b[31m  if(type==='activity'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading activities…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/activities?active=eq.true&order=created_at.desc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m      const acts=await r.json();\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='activity').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=acts.filter(a=>!granted.includes(a.id));\u001b[39m\n\u001b[31m      if(!avail.length){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All activities already granted.</p>`; document.getElementById('grant-btn').disabled=true; return; }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(a=>`<option value=\\\"${esc(a.id)}\\\" data-lbl=\\\"${esc(a.title||a.id)}\\\">${esc(a.title||a.id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load activities</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  } else if(type==='campaign'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading campaigns…</p>`;\u001b[39m\n\u001b[31m    try{\u001b[39m\n\u001b[31m      const r=await fetch(`${API}/campaigns?status=eq.active&order=created_at.desc&limit=100`,{headers:INST_RD});\u001b[39m\n\u001b[31m      const camps=r.ok?await r.json():[];\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='campaign').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=camps.filter(c=>!granted.includes(c.campaign_id));\u001b[39m\n\u001b[31m      if(!avail.length){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All active campaigns already granted.</p>`;document.getElementById('grant-btn').disabled=true;return;}\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Campaign</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c.campaign_id)}\\\" data-lbl=\\\"${esc(c.campaign_id)}\\\">${esc(c.campaign_id)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    }catch(e){wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load campaigns</p>`;document.getElementById('grant-btn').disabled=true;}\u001b[39m\n\u001b[31m  } else if(type==='recce'){\u001b[39m\n\u001b[31m    wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">Loading client list…</p>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const r=await fetch('/recce/recce-cfg.json?t='+Date.now());\u001b[39m\n\u001b[31m      const cfg=r.ok?await r.json():{clients:[]};\u001b[39m\n\u001b[31m      const clientList=(cfg.clients||[]);\u001b[39m\n\u001b[31m      const granted=currentGrants.filter(g=>g.resource_type==='recce').map(g=>g.resource_id);\u001b[39m\n\u001b[31m      const avail=clientList.filter(c=>!granted.includes(c));\u001b[39m\n\u001b[31m      if(!avail.length&&!clientList.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">No clients configured yet — add clients in Recce Settings first.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if(!avail.length){\u001b[39m\n\u001b[31m        wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--muted)\\\">All configured clients already granted.</p>`; document.getElementById('grant-btn').disabled=true; return;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      wrap.innerHTML=`<div><div class=\\\"lbl\\\">Client (who can this account see recces for?)</div>\u001b[39m\n\u001b[31m        <select id=\\\"grant-res\\\">${avail.map(c=>`<option value=\\\"${esc(c)}\\\" data-lbl=\\\"${esc(c)}\\\">${esc(c)}</option>`).join('')}</select></div>`;\u001b[39m\n\u001b[31m    } catch(e){ wrap.innerHTML=`<p style=\\\"font-size:.83rem;color:var(--red)\\\">Failed to load client list — check Recce is accessible</p>`; document.getElementById('grant-btn').disabled=true; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function doAddGrant(){\u001b[39m\n\u001b[31m  const type=document.getElementById('grant-type').value;\u001b[39m\n\u001b[31m  const sel=document.getElementById('grant-res');\u001b[39m\n\u001b[31m  if(!sel){ toast('Select a resource','err'); return; }\u001b[39m\n\u001b[31m  const rid=sel.value;\u001b[39m\n\u001b[31m  const lbl=sel.options[sel.selectedIndex]?.getAttribute('data-lbl')||rid;\u001b[39m\n\u001b[31m  const btn=document.getElementById('grant-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Adding…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/access_grants`,{method:'POST',headers:CL_H,body:JSON.stringify({account_id:currentAcctId,resource_type:type,resource_id:rid,label:lbl})});\u001b[39m\n\u001b[31m    if(!r.ok){ const t=await r.text(); toast(t.includes('unique')?'Grant already exists':'Failed','err'); return; }\u001b[39m\n\u001b[31m    toast('Grant added','ok');\u001b[39m\n\u001b[31m    closeModal('modal-add-grant');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Add Grant'; }\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function removeGrant(id){\u001b[39m\n\u001b[31m  if(!confirm('Remove this access grant?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await fetch(`${API}/access_grants?id=eq.${id}`,{method:'DELETE',headers:CL_H});\u001b[39m\n\u001b[31m    toast('Grant removed','ok');\u001b[39m\n\u001b[31m    await loadGrants(currentAcctId);\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Edit Zone / Head Status ── */\u001b[39m\n\u001b[31masync function openEditZoneHead(){\u001b[39m\n\u001b[31m  if(!currentAcctId) return;\u001b[39m\n\u001b[31m  const sel=document.getElementById('ez-zone');\u001b[39m\n\u001b[31m  sel.innerHTML='<option value=\\\"\\\">— No zone —</option>';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const zs=await fetchJ(`${API}/zones?order=zone_name`,{headers:CL_RD});\u001b[39m\n\u001b[31m    zs.forEach(z=>sel.insertAdjacentHTML('beforeend',`<option value=\\\"${esc(z.zone_name)}\\\">${esc(z.zone_name)}</option>`));\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  // Pre-fill current values\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const [a]=await fetchJ(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    if(a){sel.value=a.zone||''; document.getElementById('ez-ishead').checked=!!a.is_head;}\u001b[39m\n\u001b[31m  }catch(_){}\u001b[39m\n\u001b[31m  openModal('modal-edit-zone');\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31masync function saveZoneHead(){\u001b[39m\n\u001b[31m  const zone=document.getElementById('ez-zone').value||null;\u001b[39m\n\u001b[31m  const isHead=document.getElementById('ez-ishead').checked;\u001b[39m\n\u001b[31m  const btn=document.getElementById('ez-btn');\u001b[39m\n\u001b[31m  btn.disabled=true;btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/accounts?id=eq.${encodeURIComponent(currentAcctId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:CL_H,body:JSON.stringify({zone,is_head:isHead})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok){toast('Save failed','err');return;}\u001b[39m\n\u001b[31m    toast('Zone / Head status updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-edit-zone');\u001b[39m\n\u001b[31m    await openAccount(currentAcctId);\u001b[39m\n\u001b[31m  }catch(e){toast('Network error','err');}\u001b[39m\n\u001b[31m  finally{btn.disabled=false;btn.textContent='Save';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Reset PIN ── */\u001b[39m\n\u001b[31mfunction openResetPin(){ document.getElementById('rp-pin').value=''; openModal('modal-reset-pin'); }\u001b[39m\n\u001b[31masync function doResetPin(){\u001b[39m\n\u001b[31m  const pin=document.getElementById('rp-pin').value.trim();\u001b[39m\n\u001b[31m  if(!pin||pin.length<4){ toast('PIN must be at least 4 digits','err'); return; }\u001b[39m\n\u001b[31m  const btn=document.getElementById('rp-btn');\u001b[39m\n\u001b[31m  btn.disabled=true; btn.textContent='Saving…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/rpc/reset_client_pin`,{method:'POST',headers:CL_H,body:JSON.stringify({p_account_id:currentAcctId,p_new_pin:pin})});\u001b[39m\n\u001b[31m    if(!r.ok){ toast('Failed to reset PIN','err'); return; }\u001b[39m\n\u001b[31m    toast('PIN updated','ok');\u001b[39m\n\u001b[31m    closeModal('modal-reset-pin');\u001b[39m\n\u001b[31m  } catch(e){ toast('Failed','err'); }\u001b[39m\n\u001b[31m  finally { btn.disabled=false; btn.textContent='Set New PIN'; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Login tab switcher ── */\u001b[39m\n\u001b[31mfunction setClientLoginMode(mode){\u001b[39m\n\u001b[31m  const isPin=mode==='pin';\u001b[39m\n\u001b[31m  document.getElementById('tab-pin').classList.toggle('active',isPin);\u001b[39m\n\u001b[31m  document.getElementById('tab-code').classList.toggle('active',!isPin);\u001b[39m\n\u001b[31m  document.getElementById('cl-pin-tab').style.display=isPin?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cl-code-tab').style.display=isPin?'none':'';\u001b[39m\n\u001b[31m  if(!isPin) loadCCCompanies();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Company code login ── */\u001b[39m\n\u001b[31masync function loadCCCompanies(){\u001b[39m\n\u001b[31m  ccCompanies=[]; ccCompanyId=null; ccPersons=[];\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/companies?active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccCompanies=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select company —</option>`+\u001b[39m\n\u001b[31m      ccCompanies.map(c=>`<option value=\\\"${c.id}\\\">${esc(c.name)}</option>`).join('');\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-company-sel').innerHTML=`<option value=\\\"\\\">Failed to load — try again</option>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction onCCCompanySelect(){\u001b[39m\n\u001b[31m  const sel=document.getElementById('cc-company-sel');\u001b[39m\n\u001b[31m  ccCompanyId=sel.value?parseInt(sel.value):null;\u001b[39m\n\u001b[31m  document.getElementById('cc-step2').style.display=ccCompanyId?'':'none';\u001b[39m\n\u001b[31m  document.getElementById('cc-step3').style.display='none';\u001b[39m\n\u001b[31m  const ci=document.getElementById('cc-company-code'); if(ci) ci.value='';\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function verifyCCCode(){\u001b[39m\n\u001b[31m  const code=document.getElementById('cc-company-code').value.trim().toLowerCase();\u001b[39m\n\u001b[31m  if(!code){ document.getElementById('cc-err').textContent='Enter company code'; return; }\u001b[39m\n\u001b[31m  if(!ccCompanyId){ document.getElementById('cc-err').textContent='Select a company first'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  if(!co||!co.code||co.code.toLowerCase()!==code){ document.getElementById('cc-err').textContent='Incorrect company code'; return; }\u001b[39m\n\u001b[31m  document.getElementById('cc-err').textContent='';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/client_access?company_id=eq.${ccCompanyId}&active=eq.true&order=name.asc`,{headers:ACT_RD});\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error();\u001b[39m\n\u001b[31m    ccPersons=await r.json();\u001b[39m\n\u001b[31m    const sel=document.getElementById('cc-person-sel');\u001b[39m\n\u001b[31m    sel.innerHTML=`<option value=\\\"\\\">— select your name —</option>`+\u001b[39m\n\u001b[31m      ccPersons.map(p=>`<option value=\\\"${p.id}\\\">${esc(p.name)}</option>`).join('');\u001b[39m\n\u001b[31m    document.getElementById('cc-ind-code').value='';\u001b[39m\n\u001b[31m    document.getElementById('cc-step3').style.display='';\u001b[39m\n\u001b[31m  } catch(e){\u001b[39m\n\u001b[31m    document.getElementById('cc-err').textContent='Error loading contacts. Try again.';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loginWithCode(){\u001b[39m\n\u001b[31m  const personId=parseInt(document.getElementById('cc-person-sel').value);\u001b[39m\n\u001b[31m  const indCode=document.getElementById('cc-ind-code').value.trim().toUpperCase();\u001b[39m\n\u001b[31m  if(!personId){ document.getElementById('cc-err').textContent='Select your name'; return; }\u001b[39m\n\u001b[31m  if(!indCode){ document.getElementById('cc-err').textContent='Enter your access code'; return; }\u001b[39m\n\u001b[31m  const ca=ccPersons.find(p=>p.id===personId);\u001b[39m\n\u001b[31m  if(!ca){ document.getElementById('cc-err').textContent='Person not found — try again'; return; }\u001b[39m\n\u001b[31m  if(!ca.access_code||ca.access_code.toUpperCase()!==indCode){ document.getElementById('cc-err').textContent='Incorrect access code'; return; }\u001b[39m\n\u001b[31m  const co=ccCompanies.find(c=>c.id===ccCompanyId);\u001b[39m\n\u001b[31m  const actSession={id:`client:${ca.id}`,name:ca.name,company:co?co.name:'',phone:ca.phone||'',\u001b[39m\n\u001b[31m    role:ca.role,type:'client',activityId:ca.activity_id,clientAccessId:ca.id,ts:Date.now()};\u001b[39m\n\u001b[31m  localStorage.setItem('lm360-activity-session',JSON.stringify(actSession));\u001b[39m\n\u001b[31m  const next=new URLSearchParams(location.search).get('next');\u001b[39m\n\u001b[31m  window.location.href=next||'/activity/';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Client home ── */\u001b[39m\n\u001b[31masync function enterClientHome(){\u001b[39m\n\u001b[31m  document.getElementById('home-name').textContent=clientSession.displayName||clientSession.id;\u001b[39m\n\u001b[31m  document.getElementById('home-welcome').textContent=clientSession.contactName?`Welcome, ${clientSession.contactName}`:'Welcome';\u001b[39m\n\u001b[31m  show('screen-home');\u001b[39m\n\u001b[31m  const tiles=document.getElementById('home-tiles');\u001b[39m\n\u001b[31m  tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const grants=await fetchJ(`${API}/access_grants?account_id=eq.${encodeURIComponent(clientSession.id)}`,{headers:CL_RD});\u001b[39m\n\u001b[31m    _clientGrants.activity=grants.filter(g=>g.resource_type==='activity');\u001b[39m\n\u001b[31m    _clientGrants.recce   =grants.filter(g=>g.resource_type==='recce');   // legacy\u001b[39m\n\u001b[31m    _clientGrants.campaign=grants.filter(g=>g.resource_type==='campaign');\u001b[39m\n\u001b[31m    _accessibleCampaignIds=await computeAccessibleCampaigns();·\u001b[39m\n\u001b[31m    let html='';\u001b[39m\n\u001b[31m    if(_clientGrants.activity.length){\u001b[39m\n\u001b[31m      const cnt=_clientGrants.activity.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterActivity()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} activit${cnt===1?'y':'ies'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📋</div><div class=\\\"tile-lbl\\\">Activity</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(_accessibleCampaignIds.length){\u001b[39m\n\u001b[31m      const cnt=_accessibleCampaignIds.length;\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" onclick=\\\"enterRecce()\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">${cnt} campaign${cnt===1?'':'s'}</div></div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      html+=`<div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">📍</div><div class=\\\"tile-lbl\\\">Recce</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">No access</div></div>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    html+=`\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏗️</div><div class=\\\"tile-lbl\\\">Installation</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" style=\\\"opacity:.45;cursor:default\\\">\u001b[39m\n\u001b[31m        <div class=\\\"tile-icon\\\">🏭</div><div class=\\\"tile-lbl\\\">Production</div>\u001b[39m\n\u001b[31m        <div class=\\\"tile-cnt\\\">Coming soon</div></div>`;\u001b[39m\n\u001b[31m    tiles.innerHTML=html;\u001b[39m\n\u001b[31m  } catch(e){ tiles.innerHTML=`<div class=\\\"empty\\\" style=\\\"grid-column:span 2\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function computeAccessibleCampaigns(){\u001b[39m\n\u001b[31m  if(!_clientGrants.campaign.length) return [];\u001b[39m\n\u001b[31m  const grantedIds=_clientGrants.campaign.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  // is_head: bypass zone filter (but grants still required)\u001b[39m\n\u001b[31m  if(clientSession.isHead) return grantedIds;\u001b[39m\n\u001b[31m  // No zone set: no zone filter\u001b[39m\n\u001b[31m  if(!clientSession.zone) return grantedIds;\u001b[39m\n\u001b[31m  // Zone rep: filter campaigns where session.zone is in campaign.zones\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const ids=grantedIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    const camps=await fetchJ(`${API}/campaigns?campaign_id=in.(${ids})`,{headers:INST_RD});\u001b[39m\n\u001b[31m    return camps.filter(c=>(c.zones||[]).includes(clientSession.zone)).map(c=>c.campaign_id);\u001b[39m\n\u001b[31m  }catch(_){\u001b[39m\n\u001b[31m    return grantedIds; // fallback on error\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Activity responses ── */\u001b[39m\n\u001b[31masync function enterActivity(){\u001b[39m\n\u001b[31m  const ids=_clientGrants.activity.map(g=>g.resource_id);\u001b[39m\n\u001b[31m  if(!ids.length) return;\u001b[39m\n\u001b[31m  show('screen-responses');\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _areaF.clear(); _dateF.clear(); _leadF.clear();\u001b[39m\n\u001b[31m  allResponses=[]; filteredResp=[];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [acts,resps]=await Promise.all([\u001b[39m\n\u001b[31m      fetchJ(`${API}/activities?id=in.(${ids.map(encodeURIComponent).join(',')})`,{headers:ACT_RD}),\u001b[39m\n\u001b[31m      fetchJ(`${API}/responses?activity_id=in.(${ids.map(encodeURIComponent).join(',')})&gform_submitted=eq.true&order=submitted_at.desc`,{headers:ACT_RD})\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    acts.forEach(a=>{actCache[a.id]=a;});\u001b[39m\n\u001b[31m    allResponses=resps;\u001b[39m\n\u001b[31m    const cnt=allResponses.length;\u001b[39m\n\u001b[31m    document.getElementById('resp-sub').textContent=`${cnt} response${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderFilters();\u001b[39m\n\u001b[31m    applyFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('resp-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce view ── */\u001b[39m\n\u001b[31masync function enterRecce(){\u001b[39m\n\u001b[31m  if(!_accessibleCampaignIds.length) return;\u001b[39m\n\u001b[31m  show('screen-recce');\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent='Loading…';\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML='';\u001b[39m\n\u001b[31m  document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  _recceRows=[]; _recceDateF.clear(); _recceBrandF.clear();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const ids=_accessibleCampaignIds.map(encodeURIComponent).join(',');\u001b[39m\n\u001b[31m    _recceRows=await fetchJ(`${API}/submissions?campaign_id=in.(${ids})&client_archived=eq.false&deleted_at=is.null&order=submitted_at.desc`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const cnt=_recceRows.length;\u001b[39m\n\u001b[31m    document.getElementById('recce-sub').textContent=`${cnt} recce${cnt!==1?'s':''}`;\u001b[39m\n\u001b[31m    renderRecceFilters();\u001b[39m\n\u001b[31m    applyRecceFilters();\u001b[39m\n\u001b[31m  } catch(e){ document.getElementById('recce-list').innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRecceFilters(){\u001b[39m\n\u001b[31m  const dates=[...new Set(_recceRows.map(r=>r.visit_date||r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const brands=[...new Set(_recceRows.map(r=>r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceDateF.size?' active':''}\\\" onclick=\\\"clearRF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_recceDateF.has(d)?' active':''}\\\" onclick=\\\"toggleRF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(brands.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Brand</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_recceBrandF.size?' active':''}\\\" onclick=\\\"clearRF('brand')\\\">All</div>\u001b[39m\n\u001b[31m      ${brands.map(b=>`<div class=\\\"chip${_recceBrandF.has(b)?' active':''}\\\" onclick=\\\"toggleRF('brand','${esc(b)}')\\\">${esc(b)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('recce-filters').innerHTML=html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleRF(type,val){ const s=type==='date'?_recceDateF:_recceBrandF; s.has(val)?s.delete(val):s.add(val); renderRecceFilters(); applyRecceFilters(); }\u001b[39m\n\u001b[31mfunction clearRF(type){ (type==='date'?_recceDateF:_recceBrandF).clear(); renderRecceFilters(); applyRecceFilters(); }·\u001b[39m\n\u001b[31mfunction applyRecceFilters(){\u001b[39m\n\u001b[31m  let rows=_recceRows.filter(r=>{\u001b[39m\n\u001b[31m    const d=r.visit_date||r.submitted_at?.slice(0,10)||'';\u001b[39m\n\u001b[31m    if(_recceDateF.size&&!_recceDateF.has(d)) return false;\u001b[39m\n\u001b[31m    if(_recceBrandF.size&&!_recceBrandF.has(r.brand||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=_recceRows.length,shown=rows.length;\u001b[39m\n\u001b[31m  document.getElementById('recce-sub').textContent=shown<total?`${shown} of ${total} recces`:`${total} recce${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRecceList(rows);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mconst _STATUS_CFG={\u001b[39m\n\u001b[31m  pending:               {label:'Awaiting Review', cls:'b-muted'},\u001b[39m\n\u001b[31m  approved:              {label:'Approved ✓',      cls:'b-green'},\u001b[39m\n\u001b[31m  rejected:              {label:'Rejected',         cls:'b-hot'},\u001b[39m\n\u001b[31m  approved_with_changes: {label:'Approved w/ Comments', cls:'b-warm'},\u001b[39m\n\u001b[31m};\u001b[39m\n\u001b[31mfunction renderRecceList(rows){\u001b[39m\n\u001b[31m  const el=document.getElementById('recce-list');\u001b[39m\n\u001b[31m  if(!rows.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📍</div><p>No recces match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=rows.map(r=>{\u001b[39m\n\u001b[31m    const dt=r.visit_date?new Date(r.visit_date+'T00:00:00'):new Date(r.submitted_at);\u001b[39m\n\u001b[31m    const date=dt.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m    const photos=(r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[r.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const chips=[];\u001b[39m\n\u001b[31m    if(r.brand) chips.push(`<span class=\\\"badge b-muted\\\">🏷 ${esc(r.brand)}</span>`);\u001b[39m\n\u001b[31m    if(r.city)  chips.push(`<span class=\\\"badge b-muted\\\">📍 ${esc(r.city)}</span>`);\u001b[39m\n\u001b[31m    if(photos)  chips.push(`<span class=\\\"badge b-muted\\\">📷 ${photos}</span>`);\u001b[39m\n\u001b[31m    if(r.has_branding) chips.push(`<span class=\\\"badge b-muted\\\">🪧 ${r.branding_count||''}</span>`);\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" style=\\\"margin-bottom:8px\\\" onclick=\\\"enterRecceApproval('${esc(r.sub_id)}')\\\">\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:flex-start;justify-content:space-between;gap:8px\\\">\u001b[39m\n\u001b[31m        <div class=\\\"card-name\\\">${esc(r.store_name||'Unknown')}</div>\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"flex-shrink:0;white-space:nowrap\\\">${sc.label}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\" style=\\\"margin-top:6px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m        ${chips.join('')}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:.75rem;color:${r.client_status==='pending'?'var(--primary)':'var(--muted)'};margin-top:6px\\\">\u001b[39m\n\u001b[31m        ${r.client_status==='pending'?'Tap to review →':'Tap to view details →'}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderFilters(){\u001b[39m\n\u001b[31m  const areas=[...new Set(allResponses.map(r=>r.area).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const dates=[...new Set(allResponses.map(r=>r.submitted_at?.slice(0,10)).filter(Boolean))].sort().reverse();\u001b[39m\n\u001b[31m  const leads=['Hot','Warm','Cold'].filter(l=>allResponses.some(r=>(r.form_data||{}).lead_type===l));\u001b[39m\n\u001b[31m  let html='';\u001b[39m\n\u001b[31m  if(areas.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Area</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_areaF.size?' active':''}\\\" onclick=\\\"clearF('area')\\\">All</div>\u001b[39m\n\u001b[31m      ${areas.map(a=>`<div class=\\\"chip${_areaF.has(a)?' active':''}\\\" onclick=\\\"toggleF('area','${esc(a)}')\\\">${esc(a)}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(dates.length>1){\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Date</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_dateF.size?' active':''}\\\" onclick=\\\"clearF('date')\\\">All</div>\u001b[39m\n\u001b[31m      ${dates.map(d=>{\u001b[39m\n\u001b[31m        const disp=new Date(d+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short'});\u001b[39m\n\u001b[31m        return `<div class=\\\"chip${_dateF.has(d)?' active':''}\\\" onclick=\\\"toggleF('date','${d}')\\\">${disp}</div>`;\u001b[39m\n\u001b[31m      }).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if(leads.length>1){\u001b[39m\n\u001b[31m    const lmap={Hot:'🔴 Hot',Warm:'🟡 Warm',Cold:'🔵 Cold'};\u001b[39m\n\u001b[31m    html+=`<div><div class=\\\"sec-hdr\\\">Lead Type</div><div class=\\\"filter-row\\\">\u001b[39m\n\u001b[31m      <div class=\\\"chip${!_leadF.size?' active':''}\\\" onclick=\\\"clearF('lead')\\\">All</div>\u001b[39m\n\u001b[31m      ${leads.map(l=>`<div class=\\\"chip${_leadF.has(l)?' active':''}\\\" onclick=\\\"toggleF('lead','${l}')\\\">${lmap[l]}</div>`).join('')}\u001b[39m\n\u001b[31m    </div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('resp-filters').innerHTML=html;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction toggleF(type,val){ const s=type==='area'?_areaF:type==='date'?_dateF:_leadF; s.has(val)?s.delete(val):s.add(val); renderFilters(); applyFilters(); }\u001b[39m\n\u001b[31mfunction clearF(type){ (type==='area'?_areaF:type==='date'?_dateF:_leadF).clear(); renderFilters(); applyFilters(); }·\u001b[39m\n\u001b[31mfunction applyFilters(){\u001b[39m\n\u001b[31m  filteredResp=allResponses.filter(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    if(_areaF.size&&!_areaF.has(r.area||'')) return false;\u001b[39m\n\u001b[31m    if(_dateF.size&&!_dateF.has(r.submitted_at?.slice(0,10)||'')) return false;\u001b[39m\n\u001b[31m    if(_leadF.size&&!_leadF.has(fd.lead_type||'')) return false;\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const total=allResponses.length,shown=filteredResp.length;\u001b[39m\n\u001b[31m  document.getElementById('resp-sub').textContent=shown<total?`${shown} of ${total} responses`:`${total} response${total!==1?'s':''}`;\u001b[39m\n\u001b[31m  renderRespList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderRespList(){\u001b[39m\n\u001b[31m  const el=document.getElementById('resp-list');\u001b[39m\n\u001b[31m  if(!filteredResp.length){ el.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">📋</div><p>No responses match your filter</p></div>`; return; }\u001b[39m\n\u001b[31m  el.innerHTML=filteredResp.map(r=>{\u001b[39m\n\u001b[31m    const fd=r.form_data||{};\u001b[39m\n\u001b[31m    const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m    const date=r.submitted_at?new Date(r.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}):'';\u001b[39m\n\u001b[31m    return `<div class=\\\"card\\\" onclick=\\\"openPres(${r.id})\\\" style=\\\"margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"card-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-company\\\">${esc(fd.company_name||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"card-meta\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge b-${lead.toLowerCase()}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m        ${r.photo_count>0?`<span class=\\\"badge b-muted\\\">📸 ${r.photo_count}</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span style=\\\"font-size:.76rem;color:var(--muted)\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:.75rem;color:var(--muted);margin-left:auto\\\">${date}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Presentation ── */\u001b[39m\n\u001b[31masync function openPres(id){\u001b[39m\n\u001b[31m  const r=allResponses.find(x=>x.id===id); if(!r) return;\u001b[39m\n\u001b[31m  show('screen-pres');\u001b[39m\n\u001b[31m  const body=document.getElementById('pres-body');\u001b[39m\n\u001b[31m  body.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  let photos=[];\u001b[39m\n\u001b[31m  try { const pr=await fetch(`${API}/response_photos?response_id=eq.${id}&order=captured_at.asc`,{headers:ACT_RD}); photos=await pr.json(); } catch(e){}\u001b[39m\n\u001b[31m  const fd=r.form_data||{};\u001b[39m\n\u001b[31m  const act=actCache[r.activity_id];\u001b[39m\n\u001b[31m  const schema=act?.form_schema||[];\u001b[39m\n\u001b[31m  const lead=fd.lead_type||'';\u001b[39m\n\u001b[31m  const lc=lead==='Hot'?'var(--hot)':lead==='Warm'?'var(--amber)':lead==='Cold'?'var(--cold)':'var(--muted)';\u001b[39m\n\u001b[31m  const date=r.submitted_at?new Date(r.submitted_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}):'';\u001b[39m\n\u001b[31m  let fieldsHtml='';\u001b[39m\n\u001b[31m  schema.forEach(f=>{ const v=fd[f.key]; if(!v) return; const wide=f.type==='textarea'||['area','remarks'].includes(f.key);\u001b[39m\n\u001b[31m    fieldsHtml+=`<div class=\\\"pres-field${wide?' wide':''}\\\"><div class=\\\"pres-flbl\\\">${esc(f.label)}</div><div class=\\\"pres-fval\\\">${esc(v)}</div></div>`; });\u001b[39m\n\u001b[31m  lbPhotos=photos.map(p=>p.url); lbIdx=0;\u001b[39m\n\u001b[31m  let photosHtml='';\u001b[39m\n\u001b[31m  photos.forEach((p,i)=>{ const full=photos.length%2===1&&i===photos.length-1;\u001b[39m\n\u001b[31m    photosHtml+=`<div class=\\\"photo-cell${full?' full':''}\\\" onclick=\\\"openLb(${i})\\\"><img src=\\\"${esc(p.url)}\\\" loading=\\\"lazy\\\" alt=\\\"\\\"></div>`; });\u001b[39m\n\u001b[31m  body.innerHTML=`\u001b[39m\n\u001b[31m    <div class=\\\"pres-hero\\\">\u001b[39m\n\u001b[31m      <div class=\\\"pres-name\\\">${esc(fd.person_name||'—')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-company\\\">${esc(fd.company_name||act?.title||'')}</div>\u001b[39m\n\u001b[31m      <div class=\\\"pres-badges\\\">\u001b[39m\n\u001b[31m        ${lead?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:${lc}\\\">${lead} Lead</span>`:''}\u001b[39m\n\u001b[31m        ${fd.mobile?`<span class=\\\"badge\\\" style=\\\"background:rgba(0,0,0,.3);color:#ccc\\\">📱 ${esc(fd.mobile)}</span>`:''}\u001b[39m\n\u001b[31m        <span class=\\\"badge b-green\\\">✓ GForm Submitted</span>\u001b[39m\n\u001b[31m        ${r.area?`<span class=\\\"badge b-muted\\\">📍 ${esc(r.area)}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${fieldsHtml?`<div class=\\\"pres-section\\\"><h3>Details</h3><div class=\\\"pres-fields\\\">${fieldsHtml}</div></div>`:''}\u001b[39m\n\u001b[31m    ${photos.length?`<div class=\\\"pres-section\\\" style=\\\"padding-bottom:0\\\"><h3>Photos (${photos.length})</h3></div><div class=\\\"photo-grid\\\">${photosHtml}</div>`:''}\u001b[39m\n\u001b[31m    <div class=\\\"pres-section\\\" style=\\\"color:var(--muted);font-size:.74rem\\\">Captured: ${date}</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Recce Approval ── */\u001b[39m\n\u001b[31masync function enterRecceApproval(subId){\u001b[39m\n\u001b[31m  show('screen-recce-approval');\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-title').textContent='Recce Review';\u001b[39m\n\u001b[31m  document.getElementById('approval-hdr-sub').textContent='';\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    // Always re-fetch for freshness (another rep may have already reviewed)\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const sub=rows[0];\u001b[39m\n\u001b[31m    if(!sub){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Not found</p></div>`;return;}\u001b[39m\n\u001b[31m    _approvalSub=sub;\u001b[39m\n\u001b[31m    _approvalOption=null;\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-title').textContent=sub.store_name||'Recce Review';\u001b[39m\n\u001b[31m    document.getElementById('approval-hdr-sub').textContent=[sub.brand,sub.city].filter(Boolean).join(' · ');\u001b[39m\n\u001b[31m    renderApprovalScreen(sub);\u001b[39m\n\u001b[31m  }catch(e){bodyEl.innerHTML=`<div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⚠️</div><p>Failed to load</p></div>`;}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderApprovalScreen(sub){\u001b[39m\n\u001b[31m  const bodyEl=document.getElementById('approval-body');\u001b[39m\n\u001b[31m  const isPending=sub.client_status==='pending';\u001b[39m\n\u001b[31m  const dt=sub.visit_date\u001b[39m\n\u001b[31m    ?new Date(sub.visit_date+'T00:00:00').toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'})\u001b[39m\n\u001b[31m    :new Date(sub.submitted_at).toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'});\u001b[39m\n\u001b[31m  const brandings=(sub.meta||{}).brandings||[];\u001b[39m\n\u001b[31m  const brPhotos=(sub.photo_urls||{}).brandings||[];·\u001b[39m\n\u001b[31m  let html=`<div class=\\\"appr-banner\\\">\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-name\\\">${esc(sub.store_name||'—')}</div>\u001b[39m\n\u001b[31m    <div class=\\\"appr-banner-meta\\\">\u001b[39m\n\u001b[31m      ${sub.brand?`<span class=\\\"badge b-muted\\\">🏷 ${esc(sub.brand)}</span>`:''}\u001b[39m\n\u001b[31m      ${sub.city?`<span class=\\\"badge b-muted\\\">📍 ${esc(sub.city)}</span>`:''}\u001b[39m\n\u001b[31m      <span class=\\\"badge b-muted\\\">📅 ${dt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${sub.client_name?`<div class=\\\"appr-banner-client\\\">Client: ${esc(sub.client_name)}</div>`:''}\u001b[39m\n\u001b[31m    ${sub.campaign_id?`<div class=\\\"appr-banner-campaign\\\">Campaign: ${esc(sub.campaign_id)}</div>`:''}\u001b[39m\n\u001b[31m  </div>`;·\u001b[39m\n\u001b[31m  if(isPending){\u001b[39m\n\u001b[31m    const hasBr=brandings.length>0;\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Your Decision</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-opts\\\">\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-1\\\" class=\\\"appr-opt-btn appr-opt-approve\\\" onclick=\\\"selectApprovalOption('1')\\\">✓ &nbsp;Fully Approve</button>\u001b[39m\n\u001b[31m        <button id=\\\"appr-opt-2\\\" class=\\\"appr-opt-btn appr-opt-reject\\\"  onclick=\\\"selectApprovalOption('2')\\\">✕ &nbsp;Fully Reject</button>\u001b[39m\n\u001b[31m        ${hasBr?`<button id=\\\"appr-opt-3\\\" class=\\\"appr-opt-btn appr-opt-comment\\\" onclick=\\\"selectApprovalOption('3')\\\">📝 &nbsp;Approve with Branding Comments</button>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-comment-section\\\" style=\\\"display:none\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"appr-comment\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1.5px solid #3d3d3d;border-radius:8px;color:var(--text);font-size:.9rem;padding:10px 12px;outline:none;resize:vertical;margin-top:4px\\\"\u001b[39m\n\u001b[31m          placeholder=\\\"Comment (optional for approval, required for rejection)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"appr-branding-section\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        ${buildBrandingCards(brandings,brPhotos)}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <button id=\\\"appr-submit-btn\\\" class=\\\"btn\\\" style=\\\"display:none;margin-top:12px\\\" onclick=\\\"submitApproval()\\\">Submit Review</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    const sc=_STATUS_CFG[sub.client_status]||_STATUS_CFG.pending;\u001b[39m\n\u001b[31m    const approvedAt=sub.client_approved_at\u001b[39m\n\u001b[31m      ?new Date(sub.client_approved_at).toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit',timeZone:'Asia/Kolkata'}):'';\u001b[39m\n\u001b[31m    html+=`<div class=\\\"appr-section\\\">\u001b[39m\n\u001b[31m      <div class=\\\"sec-hdr\\\">Review Result</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span class=\\\"badge ${sc.cls}\\\" style=\\\"font-size:.84rem;padding:4px 12px\\\">${sc.label}</span>\u001b[39m\n\u001b[31m        ${approvedAt?`<span style=\\\"font-size:.78rem;color:var(--muted)\\\">${approvedAt}</span>`:''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      ${sub.client_comment?`<div style=\\\"margin-top:10px;font-size:.88rem;background:var(--surface2);padding:10px 12px;border-radius:8px\\\">${esc(sub.client_comment)}</div>`:''}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m    if(sub.client_status==='approved_with_changes'&&brandings.length){\u001b[39m\n\u001b[31m      html+=`<div class=\\\"appr-section\\\"><div class=\\\"sec-hdr\\\">Branding Comments</div><div id=\\\"appr-br-results\\\"><div class=\\\"empty\\\"><div class=\\\"empty-ico\\\">⏳</div></div></div></div>`;\u001b[39m\n\u001b[31m      setTimeout(()=>loadBrandingApprovalResults(sub.sub_id,brandings,brPhotos),0);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if(sub.client_approval_pdf_url){\u001b[39m\n\u001b[31m      html+=`<a href=\\\"${esc(sub.client_approval_pdf_url)}\\\" target=\\\"_blank\\\" class=\\\"btn-sec\\\" style=\\\"text-decoration:none;text-align:center;display:block\\\">⬇ Download Approval Report</a>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  bodyEl.innerHTML=html;·\u001b[39m\n\u001b[31m  // Init branding approval data for option 3\u001b[39m\n\u001b[31m  _brandingApprovalData=brandings.map((b,i)=>({\u001b[39m\n\u001b[31m    idx:i,comment:'',\u001b[39m\n\u001b[31m    proposed_type:b.type||'',proposed_w:String(b.w||''),proposed_h:String(b.h||''),\u001b[39m\n\u001b[31m    original_type:b.type||'',original_w:String(b.w||''),original_h:String(b.h||''),\u001b[39m\n\u001b[31m  }));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction buildBrandingCards(brandings,brPhotos){\u001b[39m\n\u001b[31m  if(!brandings.length) return '<p style=\\\"color:var(--muted);font-size:.84rem\\\">No branding items.</p>';\u001b[39m\n\u001b[31m  const typeOpts=['Flex Banner','Glow Sign','Vinyl Sticker','LED Board','Sunboard','Acrylic Board','Other'];\u001b[39m\n\u001b[31m  return brandings.map((b,i)=>{\u001b[39m\n\u001b[31m    const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m    const opts=typeOpts.map(t=>`<option value=\\\"${esc(t)}\\\"${b.type===t?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    return `<div class=\\\"appr-br-card\\\" id=\\\"appr-br-${i}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-title\\\">Branding Item ${i+1}</div>\u001b[39m\n\u001b[31m      <div class=\\\"appr-br-inner\\\">\u001b[39m\n\u001b[31m        <div>${photoUrl\u001b[39m\n\u001b[31m          ?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`\u001b[39m\n\u001b[31m          :`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-specs\\\">\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Recce Specs</div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Type</span><span>${esc(b.type||'—')}</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">W</span><span>${b.w||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">H</span><span>${b.h||'—'}\\\"</span></div>\u001b[39m\n\u001b[31m            <div class=\\\"appr-br-spec-row\\\"><span class=\\\"appr-br-lbl\\\">Qty</span><span>${b.qty||'—'}</span></div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"appr-br-col\\\">\u001b[39m\n\u001b[31m            <div class=\\\"sec-hdr\\\" style=\\\"margin-bottom:5px\\\">Proposed</div>\u001b[39m\n\u001b[31m            <select oninput=\\\"_brandingApprovalData[${i}].proposed_type=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m              <option value=\\\"${esc(b.type||'')}\\\">Same</option>${opts}\u001b[39m\n\u001b[31m            </select>\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.w||''}\\\" placeholder=\\\"W (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_w=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none;margin-bottom:3px\\\">\u001b[39m\n\u001b[31m            <input type=\\\"number\\\" value=\\\"${b.h||''}\\\" placeholder=\\\"H (in)\\\"\u001b[39m\n\u001b[31m              oninput=\\\"_brandingApprovalData[${i}].proposed_h=this.value\\\"\u001b[39m\n\u001b[31m              style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.78rem;padding:5px 7px;outline:none\\\">\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=\\\"padding:0 12px 10px\\\">\u001b[39m\n\u001b[31m        <textarea placeholder=\\\"Comment for this item (optional)\\\"\u001b[39m\n\u001b[31m          oninput=\\\"_brandingApprovalData[${i}].comment=this.value\\\" rows=\\\"2\\\"\u001b[39m\n\u001b[31m          style=\\\"width:100%;background:#141414;border:1px solid #3d3d3d;border-radius:6px;color:var(--text);font-size:.82rem;padding:7px 10px;outline:none;resize:vertical\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction selectApprovalOption(opt){\u001b[39m\n\u001b[31m  _approvalOption=opt;\u001b[39m\n\u001b[31m  ['1','2','3'].forEach(o=>{\u001b[39m\n\u001b[31m    const btn=document.getElementById(`appr-opt-${o}`);\u001b[39m\n\u001b[31m    if(btn) btn.classList.toggle('active',o===opt);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const cSec=document.getElementById('appr-comment-section');\u001b[39m\n\u001b[31m  const bSec=document.getElementById('appr-branding-section');\u001b[39m\n\u001b[31m  const sub =document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(cSec) cSec.style.display=(opt==='1'||opt==='2')?'':'none';\u001b[39m\n\u001b[31m  if(bSec) bSec.style.display=(opt==='3')?'':'none';\u001b[39m\n\u001b[31m  if(sub){\u001b[39m\n\u001b[31m    sub.style.display='';\u001b[39m\n\u001b[31m    sub.style.background=opt==='1'?'var(--green)':opt==='2'?'var(--red)':'var(--amber)';\u001b[39m\n\u001b[31m    sub.style.color=opt==='3'?'#000':'#fff';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const cEl=document.getElementById('appr-comment');\u001b[39m\n\u001b[31m  if(cEl) cEl.placeholder=opt==='2'?'Reason for rejection (required)':'Comment (optional)';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function submitApproval(){\u001b[39m\n\u001b[31m  if(!_approvalSub||!_approvalOption){toast('Select an option first','err');return;}\u001b[39m\n\u001b[31m  const subId=_approvalSub.sub_id;\u001b[39m\n\u001b[31m  const btn=document.getElementById('appr-submit-btn');\u001b[39m\n\u001b[31m  if(btn){btn.disabled=true;btn.textContent='Submitting…';}\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const patch={client_approved_by:clientSession.id,client_approved_at:new Date().toISOString()};\u001b[39m\n\u001b[31m    if(_approvalOption==='1'){\u001b[39m\n\u001b[31m      patch.client_status='approved';\u001b[39m\n\u001b[31m      patch.client_comment=document.getElementById('appr-comment')?.value.trim()||null;\u001b[39m\n\u001b[31m    }else if(_approvalOption==='2'){\u001b[39m\n\u001b[31m      const cmt=document.getElementById('appr-comment')?.value.trim()||'';\u001b[39m\n\u001b[31m      if(!cmt){toast('Please provide a reason for rejection','err');return;}\u001b[39m\n\u001b[31m      patch.client_status='rejected';\u001b[39m\n\u001b[31m      patch.client_comment=cmt;\u001b[39m\n\u001b[31m    }else{\u001b[39m\n\u001b[31m      patch.client_status='approved_with_changes';\u001b[39m\n\u001b[31m      patch.client_comment=null;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const r=await fetch(`${API}/submissions?sub_id=eq.${encodeURIComponent(subId)}`,{\u001b[39m\n\u001b[31m      method:'PATCH',headers:RC_WR,body:JSON.stringify(patch)\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if(!r.ok) throw new Error('Update failed');·\u001b[39m\n\u001b[31m    // Option 3: insert only changed/commented branding rows\u001b[39m\n\u001b[31m    if(_approvalOption==='3'){\u001b[39m\n\u001b[31m      for(const d of _brandingApprovalData){\u001b[39m\n\u001b[31m        const changed=d.comment.trim()||d.proposed_type!==d.original_type||\u001b[39m\n\u001b[31m                      d.proposed_w!==d.original_w||d.proposed_h!==d.original_h;\u001b[39m\n\u001b[31m        if(!changed) continue;\u001b[39m\n\u001b[31m        await fetch(`${API}/branding_approvals`,{\u001b[39m\n\u001b[31m          method:'POST',\u001b[39m\n\u001b[31m          headers:{...RC_WR,'Prefer':'return=minimal'},\u001b[39m\n\u001b[31m          body:JSON.stringify({\u001b[39m\n\u001b[31m            submission_id:subId,branding_idx:d.idx,\u001b[39m\n\u001b[31m            comment:d.comment.trim()||null,\u001b[39m\n\u001b[31m            proposed_type:d.proposed_type!==d.original_type?d.proposed_type:null,\u001b[39m\n\u001b[31m            proposed_w:d.proposed_w!==d.original_w?(parseFloat(d.proposed_w)||null):null,\u001b[39m\n\u001b[31m            proposed_h:d.proposed_h!==d.original_h?(parseFloat(d.proposed_h)||null):null,\u001b[39m\n\u001b[31m            approved_by:clientSession.id,\u001b[39m\n\u001b[31m          })\u001b[39m\n\u001b[31m        });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Fire-and-forget PDF (Step 7 — endpoint not yet implemented)\u001b[39m\n\u001b[31m    fetch('/slides-proxy',{method:'POST',headers:{'Content-Type':'application/json'},\u001b[39m\n\u001b[31m      body:JSON.stringify({type:'client-approval-report',submissionId:subId,empId:clientSession.id})\u001b[39m\n\u001b[31m    }).catch(()=>{}); // TODO: Step 7·\u001b[39m\n\u001b[31m    toast('Review submitted ✓','ok');\u001b[39m\n\u001b[31m    Object.assign(_approvalSub,patch);\u001b[39m\n\u001b[31m    // Update cached list row\u001b[39m\n\u001b[31m    const idx=_recceRows.findIndex(x=>x.sub_id===subId);\u001b[39m\n\u001b[31m    if(idx>=0) Object.assign(_recceRows[idx],patch);\u001b[39m\n\u001b[31m    renderApprovalScreen(_approvalSub);\u001b[39m\n\u001b[31m  }catch(e){\u001b[39m\n\u001b[31m    toast('Failed — try again','err');\u001b[39m\n\u001b[31m  }finally{\u001b[39m\n\u001b[31m    if(btn){btn.disabled=false;btn.textContent='Submit Review';}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadBrandingApprovalResults(subId,brandings,brPhotos){\u001b[39m\n\u001b[31m  const el=document.getElementById('appr-br-results');\u001b[39m\n\u001b[31m  if(!el) return;\u001b[39m\n\u001b[31m  try{\u001b[39m\n\u001b[31m    const rows=await fetchJ(`${API}/branding_approvals?submission_id=eq.${encodeURIComponent(subId)}&order=branding_idx`,{headers:RC_RD});\u001b[39m\n\u001b[31m    const byIdx={};\u001b[39m\n\u001b[31m    rows.forEach(r=>{byIdx[r.branding_idx]=r;});\u001b[39m\n\u001b[31m    el.innerHTML=brandings.map((b,i)=>{\u001b[39m\n\u001b[31m      const apr=byIdx[i];\u001b[39m\n\u001b[31m      const photoUrl=(brPhotos[i]||{}).url||null;\u001b[39m\n\u001b[31m      if(!apr) return `<div class=\\\"appr-br-card\\\" style=\\\"opacity:.55\\\"><div class=\\\"appr-br-title\\\">Item ${i+1}: No changes proposed</div></div>`;\u001b[39m\n\u001b[31m      return `<div class=\\\"appr-br-card\\\">\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-title\\\">Item ${i+1}</div>\u001b[39m\n\u001b[31m        <div class=\\\"appr-br-inner\\\" style=\\\"align-items:center\\\">\u001b[39m\n\u001b[31m          ${photoUrl?`<img src=\\\"${esc(photoUrl)}\\\" class=\\\"appr-br-photo\\\">`:`<div class=\\\"appr-br-photo-ph\\\">No photo</div>`}\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;flex-direction:column;gap:4px;font-size:.82rem\\\">\u001b[39m\n\u001b[31m            ${apr.proposed_type?`<div><span class=\\\"appr-br-lbl\\\">Type →</span> ${esc(apr.proposed_type)}</div>`:''}\u001b[39m\n\u001b[31m            ${(apr.proposed_w||apr.proposed_h)?`<div><span class=\\\"appr-br-lbl\\\">Size →</span> ${apr.proposed_w||'—'}\\\" × ${apr.proposed_h||'—'}\\\"</div>`:''}\u001b[39m\n\u001b[31m            ${apr.comment?`<div style=\\\"color:var(--amber);margin-top:2px\\\">\\\"${esc(apr.comment)}\\\"</div>`:''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m    }).join('');\u001b[39m\n\u001b[31m  }catch(e){el.innerHTML='<p style=\\\"color:var(--muted);font-size:.84rem\\\">Could not load.</p>';}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m/* ── Lightbox ── */\u001b[39m\n\u001b[31mfunction openLb(i){ lbIdx=i; document.getElementById('lb-img').src=lbPhotos[i]; document.getElementById('lightbox').classList.add('open'); }\u001b[39m\n\u001b[31mfunction closeLb(){ document.getElementById('lightbox').classList.remove('open'); }\u001b[39m\n\u001b[31mfunction lbNav(d){ lbIdx=(lbIdx+d+lbPhotos.length)%lbPhotos.length; document.getElementById('lb-img').src=lbPhotos[lbIdx]; }·\u001b[39m\n\u001b[31mdocument.addEventListener('keydown',e=>{\u001b[39m\n\u001b[31m  if(e.key==='Escape') document.querySelectorAll('.modal.open').forEach(m=>m.classList.remove('open'));\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mdocument.querySelectorAll('.modal').forEach(m=>{\u001b[39m\n\u001b[31m  m.addEventListener('click',e=>{if(e.target===m) m.classList.remove('open');});\u001b[39m\n\u001b[31m});·\u001b[39m\n\u001b[31mif('serviceWorker' in navigator) navigator.serviceWorker.register('/client/sw.js');\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  159 |     const resp = await ctx.get(BASE_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain(\"client_status:'approved'\");\n      |                 ^\n  162 |     expect(src).toContain(\"client_status:'rejected'\");\n  163 |     expect(src).toContain(\"client_status:'approved_with_changes'\");\n  164 |   });\n    at /var/www/360lm/tests/client.spec.js:161:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-05-28T12:42:43.984Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Rec-0205d-ee-approval-option-handlers-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 17,
                        "line": 161
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "9bf021294cad394269b8-4c67a6726d29f6a23ebc",
              "file": "client.spec.js",
              "line": 157,
              "column": 3
            },
            {
              "title": "source contains branding approval row insertion with change detection",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 76,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:45.284Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-ad13a0cc460004e4c45e",
              "file": "client.spec.js",
              "line": 166,
              "column": 3
            },
            {
              "title": "source contains fire-and-forget PDF call with TODO marker",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:45.423Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-0da3600c76825e3b4cf6",
              "file": "client.spec.js",
              "line": 179,
              "column": 3
            },
            {
              "title": "approval screen loads (unauthenticated shows login, not crash)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 489,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:45.470Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-87da26419baf793ab51e",
              "file": "client.spec.js",
              "line": 187,
              "column": 3
            },
            {
              "title": "source contains loadBrandingApprovalResults for read-only view",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:46.145Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-aab50e063a54ed6ff433",
              "file": "client.spec.js",
              "line": 195,
              "column": 3
            }
          ]
        },
        {
          "title": "Client Portal — Account Zone Management",
          "file": "client.spec.js",
          "line": 206,
          "column": 6,
          "specs": [
            {
              "title": "source contains openEditZoneHead and saveZoneHead",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 48,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:30.295Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-11cb0104fc2350a47863",
              "file": "client.spec.js",
              "line": 207,
              "column": 3
            },
            {
              "title": "source contains zone/isHead CSS classes",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 38,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:30.377Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-470d86cd765f49cd2dba",
              "file": "client.spec.js",
              "line": 217,
              "column": 3
            },
            {
              "title": "openNewAcct is async and loads zones",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:30.439Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-ba7fd336da02e6a7a860",
              "file": "client.spec.js",
              "line": 226,
              "column": 3
            },
            {
              "title": "source contains openEditZoneHead and saveZoneHead",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:46.196Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-75eb426536cb83f79c87",
              "file": "client.spec.js",
              "line": 207,
              "column": 3
            },
            {
              "title": "source contains zone/isHead CSS classes",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:46.225Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-e1668fa907c612b3ff20",
              "file": "client.spec.js",
              "line": 217,
              "column": 3
            },
            {
              "title": "openNewAcct is async and loads zones",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:46.263Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "9bf021294cad394269b8-19851dbf66d74ec1a354",
              "file": "client.spec.js",
              "line": 226,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "sales.spec.js",
      "file": "sales.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Sales — Smoke",
          "file": "sales.spec.js",
          "line": 10,
          "column": 6,
          "specs": [
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1135,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:30.500Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-fd930b1c8cc9b3b11669",
              "file": "sales.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 311,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:31.690Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-a00c16728b49d60f5736",
              "file": "sales.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js contains 360sales-v12",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 549,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.027Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-4dd67aec9112d1610f6a",
              "file": "sales.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 372,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:46.302Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-d466939338448fd3ee1c",
              "file": "sales.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 191,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:46.700Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-41e38a2ec94697eaa763",
              "file": "sales.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js contains 360sales-v12",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 204,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:46.910Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-dc4c1f0c0de34d2ad947",
              "file": "sales.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales — Canonical Job Form",
          "file": "sales.spec.js",
          "line": 30,
          "column": 6,
          "specs": [
            {
              "title": "Phase field exists in job form",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 24,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.645Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-ac0f27baa41e2aa80458",
              "file": "sales.spec.js",
              "line": 31,
              "column": 3
            },
            {
              "title": "Zone chips group exists in job form",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.699Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-2fb50a80064903095acd",
              "file": "sales.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "label says Generated Campaign ID not Job Name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 16,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.736Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-81024dc1d70ba049f887",
              "file": "sales.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "year placeholder is 2025 (4-digit)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 19,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.771Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-f12480b5354768d073e0",
              "file": "sales.spec.js",
              "line": 56,
              "column": 3
            },
            {
              "title": "Phase field exists in job form",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.134Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-b3aff5e89bc9d04fb6c9",
              "file": "sales.spec.js",
              "line": 31,
              "column": 3
            },
            {
              "title": "Zone chips group exists in job form",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.166Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-f00070a72d28734088d3",
              "file": "sales.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "label says Generated Campaign ID not Job Name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.194Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-1e569f4c57cf21d8d1ed",
              "file": "sales.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "year placeholder is 2025 (4-digit)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.223Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-3525d1db881808a9c5d9",
              "file": "sales.spec.js",
              "line": 56,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales — Dual-Write + Campaign Slug",
          "file": "sales.spec.js",
          "line": 65,
          "column": 6,
          "specs": [
            {
              "title": "source contains installHdr for writing to installation.campaigns",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.810Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-1f17d86da832881ec085",
              "file": "sales.spec.js",
              "line": 66,
              "column": 3
            },
            {
              "title": "source contains campaignSlug and buildCampaignIdStr helpers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.847Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-f8205d47ccbc9ca5b5fc",
              "file": "sales.spec.js",
              "line": 75,
              "column": 3
            },
            {
              "title": "source writes to /db/campaigns on new job",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.886Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-8a30f3bfb7cf4881bade",
              "file": "sales.spec.js",
              "line": 83,
              "column": 3
            },
            {
              "title": "source writes campaign_id to sales.jobs",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.928Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-72fa470ed7f54e195e91",
              "file": "sales.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "source has app-layer rollback on Step 2 fail",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:32.973Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-3c9b84d9bc443a154854",
              "file": "sales.spec.js",
              "line": 99,
              "column": 3
            },
            {
              "title": "source contains zone state and loadZoneChips",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 19,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:33.012Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-2349265f1e44a508f10a",
              "file": "sales.spec.js",
              "line": 110,
              "column": 3
            },
            {
              "title": "zone selection is required (validation in saveJob)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 17,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:33.058Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-883323d0bab5cfb37fb3",
              "file": "sales.spec.js",
              "line": 120,
              "column": 3
            },
            {
              "title": "edit mode locks campaign identity fields",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 16,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:33.102Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-fabf7a651c3ba16f8263",
              "file": "sales.spec.js",
              "line": 127,
              "column": 3
            },
            {
              "title": "source contains installHdr for writing to installation.campaigns",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.250Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-2a24057fefbed846d876",
              "file": "sales.spec.js",
              "line": 66,
              "column": 3
            },
            {
              "title": "source contains campaignSlug and buildCampaignIdStr helpers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.288Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-c07fa542d03b5c61a8bb",
              "file": "sales.spec.js",
              "line": 75,
              "column": 3
            },
            {
              "title": "source writes to /db/campaigns on new job",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.312Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-3f57c774a93fa899e132",
              "file": "sales.spec.js",
              "line": 83,
              "column": 3
            },
            {
              "title": "source writes campaign_id to sales.jobs",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.335Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-5b4223de4b40bd4cc2f2",
              "file": "sales.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "source has app-layer rollback on Step 2 fail",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.358Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-d6b971eea674ba51fb40",
              "file": "sales.spec.js",
              "line": 99,
              "column": 3
            },
            {
              "title": "source contains zone state and loadZoneChips",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.383Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-203c093e0812cb989ee3",
              "file": "sales.spec.js",
              "line": 110,
              "column": 3
            },
            {
              "title": "zone selection is required (validation in saveJob)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 8,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.409Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-f392b37aabee833cf319",
              "file": "sales.spec.js",
              "line": 120,
              "column": 3
            },
            {
              "title": "edit mode locks campaign identity fields",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.433Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-53f997c9a043895887b6",
              "file": "sales.spec.js",
              "line": 127,
              "column": 3
            }
          ]
        },
        {
          "title": "Hub PWA — Campaign Creation Removed (Step 4b)",
          "file": "sales.spec.js",
          "line": 138,
          "column": 6,
          "specs": [
            {
              "title": "hub sw.js contains 360lm-hub-v27",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 334,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:33.138Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-7ea6237dcbfd1fcb5d9b",
              "file": "sales.spec.js",
              "line": 141,
              "column": 3
            },
            {
              "title": "hub source no longer contains modal-new-campaign form fields",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:33.515Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-3f8919964aeb839afad2",
              "file": "sales.spec.js",
              "line": 147,
              "column": 3
            },
            {
              "title": "hub source has stub openNewCampaign redirecting to /sales/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:33.546Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-60b336a2df5e13cdfe78",
              "file": "sales.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "hub sw.js contains 360lm-hub-v27",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 209,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.460Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-ba5bdd6248803022661d",
              "file": "sales.spec.js",
              "line": 141,
              "column": 3
            },
            {
              "title": "hub source no longer contains modal-new-campaign form fields",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 17,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.689Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-fb80ba38634819e79910",
              "file": "sales.spec.js",
              "line": 147,
              "column": 3
            },
            {
              "title": "hub source has stub openNewCampaign redirecting to /sales/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-05-28T12:42:47.723Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-fc1538922916d8d0528e",
              "file": "sales.spec.js",
              "line": 159,
              "column": 3
            }
          ]
        }
      ]
    }
  ],
  "errors": [],
  "stats": {
    "startTime": "2026-05-28T12:42:08.156Z",
    "duration": 39672.221,
    "expected": 112,
    "skipped": 0,
    "unexpected": 6,
    "flaky": 0
  }
}
