pretzinger commited on
Commit
e786d18
·
verified ·
1 Parent(s): 3a7aca2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +204 -20
index.html CHANGED
@@ -35,6 +35,7 @@ Pressure: pursuit, withdrawal, control, appeasement, escalation
35
  Failure behavior:
36
  - If fetch fails or payload invalid, fall back to demo data and show “Data: Demo” in HUD.
37
  -->
 
38
  <!doctype html>
39
  <html lang="en">
40
  <head>
@@ -66,11 +67,11 @@ Failure behavior:
66
  border: 1px solid rgba(17, 24, 39, 0.08);
67
  border-radius: 12px;
68
  padding: 12px 12px;
69
- max-width: 520px;
70
  }
71
  #hud h1 { margin: 0 0 6px; font-size: 14px; letter-spacing: 0.02em; font-weight: 650; }
72
  #hud p { margin: 0 0 10px; font-size: 12px; line-height: 1.35; color: rgba(17, 24, 39, 0.78); }
73
- #controls { display: flex; gap: 8px; align-items: center; }
74
  button {
75
  font-size: 12px;
76
  padding: 8px 10px;
@@ -88,7 +89,33 @@ Failure behavior:
88
  border: 1px solid rgba(17, 24, 39, 0.12);
89
  background: rgba(255, 255, 255, 0.7);
90
  color: rgba(17, 24, 39, 0.78);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
 
92
  </style>
93
  </head>
94
  <body>
@@ -96,16 +123,52 @@ Failure behavior:
96
  <div id="hud">
97
  <h1>IC Map</h1>
98
  <p>Three interacting forces with exactly 15 nodes: How You Move · What You Protect · Pressure Response. Toggle “Pressure” to see the network shift.</p>
 
99
  <div id="controls">
100
  <button id="toggle">Toggle Pressure</button>
101
  <span class="pill" id="mode">Mode: Baseline</span>
 
102
  <span class="pill" id="hover">Hover: none</span>
103
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  </div>
 
105
  <div id="graph"></div>
106
  </div>
107
 
108
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  // ====== Fixed trait nodes (exactly 15) ======
110
  const GROUPS = {
111
  move: { label: "How You Move", color: getCSS("--accent2") },
@@ -113,7 +176,6 @@ Failure behavior:
113
  pressure: { label: "Pressure Response", color: getCSS("--accent") }
114
  };
115
 
116
- // 5 + 5 + 5 = 15 nodes total (no extra nodes)
117
  const move = [
118
  { id: "pace", label: "Pace", group: "move" },
119
  { id: "directness", label: "Directness", group: "move" },
@@ -140,6 +202,12 @@ Failure behavior:
140
 
141
  const nodes = [...move, ...protect, ...pressure];
142
 
 
 
 
 
 
 
143
  // Seed cluster positions (3 clusters in 3D space)
144
  seedCluster(move, { x: -80, y: 10, z: 40 });
145
  seedCluster(protect,{ x: 70, y: -10, z: -30 });
@@ -147,9 +215,9 @@ Failure behavior:
147
 
148
  // Links: dense within cluster + a few bridges between clusters (no new nodes)
149
  const links = [
150
- ...ringLinks(move),
151
- ...ringLinks(protect),
152
- ...ringLinks(pressure),
153
 
154
  // Bridges: minimal, purposeful
155
  { source: "directness", target: "priority_2" },
@@ -163,6 +231,11 @@ Failure behavior:
163
  const el = document.getElementById("graph");
164
  const hoverEl = document.getElementById("hover");
165
  const modeEl = document.getElementById("mode");
 
 
 
 
 
166
 
167
  let isPressure = false;
168
  let hoveredNodeId = null;
@@ -217,6 +290,122 @@ Failure behavior:
217
  Graph.height(window.innerHeight);
218
  });
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  // ====== Helpers ======
221
  function seedCluster(arr, center) {
222
  arr.forEach((n, i) => {
@@ -227,32 +416,27 @@ Failure behavior:
227
  });
228
  }
229
 
230
- function ringLinks(arr) {
231
  const out = [];
232
- for (let i = 0; i < arr.length; i++) {
233
- out.push({ source: arr[i].id, target: arr[(i + 1) % arr.length].id });
234
- // add one chord for "neural" density
235
- out.push({ source: arr[i].id, target: arr[(i + 2) % arr.length].id });
236
  }
237
  return out;
238
  }
239
 
240
  function nodeVal(n) {
241
- // Baseline: uniform
242
- // Pressure: pressure cluster becomes larger, plus one "dominant" node based on hover
243
- const base = 1.8;
244
- if (!isPressure) return base;
245
-
246
- const groupBoost = (n.group === "pressure") ? 1.4 : (n.group === "protect" ? 1.1 : 1.0);
247
  const hoverBoost = (hoveredNodeId && n.id === hoveredNodeId) ? 2.2 : 1.0;
248
- return base * groupBoost * hoverBoost;
 
 
 
 
249
  }
250
 
251
  function nodeColor(n) {
252
  const base = GROUPS[n.group].color;
253
  if (!hoveredNodeId) return base;
254
-
255
- // Dim non-hover nodes slightly for clarity
256
  if (n.id === hoveredNodeId) return base;
257
  return "rgba(17,24,39,0.35)";
258
  }
 
35
  Failure behavior:
36
  - If fetch fails or payload invalid, fall back to demo data and show “Data: Demo” in HUD.
37
  -->
38
+
39
  <!doctype html>
40
  <html lang="en">
41
  <head>
 
67
  border: 1px solid rgba(17, 24, 39, 0.08);
68
  border-radius: 12px;
69
  padding: 12px 12px;
70
+ max-width: 560px;
71
  }
72
  #hud h1 { margin: 0 0 6px; font-size: 14px; letter-spacing: 0.02em; font-weight: 650; }
73
  #hud p { margin: 0 0 10px; font-size: 12px; line-height: 1.35; color: rgba(17, 24, 39, 0.78); }
74
+ #controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 10px; }
75
  button {
76
  font-size: 12px;
77
  padding: 8px 10px;
 
89
  border: 1px solid rgba(17, 24, 39, 0.12);
90
  background: rgba(255, 255, 255, 0.7);
91
  color: rgba(17, 24, 39, 0.78);
92
+ white-space: nowrap;
93
+ }
94
+ #legend {
95
+ display: grid;
96
+ grid-template-columns: 1fr;
97
+ gap: 8px;
98
+ }
99
+ .legendBlock {
100
+ border: 1px solid rgba(17, 24, 39, 0.08);
101
+ border-radius: 10px;
102
+ padding: 8px 10px;
103
+ background: rgba(255, 255, 255, 0.55);
104
+ }
105
+ .legendTitle {
106
+ font-size: 11px;
107
+ font-weight: 650;
108
+ margin: 0 0 6px;
109
+ color: rgba(17, 24, 39, 0.88);
110
+ }
111
+ .legendList {
112
+ margin: 0;
113
+ padding-left: 14px;
114
+ font-size: 11px;
115
+ line-height: 1.35;
116
+ color: rgba(17, 24, 39, 0.78);
117
  }
118
+ .legendList li { margin: 2px 0; }
119
  </style>
120
  </head>
121
  <body>
 
123
  <div id="hud">
124
  <h1>IC Map</h1>
125
  <p>Three interacting forces with exactly 15 nodes: How You Move · What You Protect · Pressure Response. Toggle “Pressure” to see the network shift.</p>
126
+
127
  <div id="controls">
128
  <button id="toggle">Toggle Pressure</button>
129
  <span class="pill" id="mode">Mode: Baseline</span>
130
+ <span class="pill" id="data">Data: Demo</span>
131
  <span class="pill" id="hover">Hover: none</span>
132
  </div>
133
+
134
+ <div id="legend">
135
+ <div class="legendBlock">
136
+ <div class="legendTitle">How You Move</div>
137
+ <ul class="legendList" id="legend-move"></ul>
138
+ </div>
139
+ <div class="legendBlock">
140
+ <div class="legendTitle">What You Protect</div>
141
+ <ul class="legendList" id="legend-protect"></ul>
142
+ </div>
143
+ <div class="legendBlock">
144
+ <div class="legendTitle">Pressure Response</div>
145
+ <ul class="legendList" id="legend-pressure"></ul>
146
+ </div>
147
+ </div>
148
  </div>
149
+
150
  <div id="graph"></div>
151
  </div>
152
 
153
  <script>
154
+ // ====== Config ======
155
+ // Default API base when api_base is not provided.
156
+ // REQUIRED_INPUT_PROD_API_BASE should be your production base, e.g. https://intimacy-compass.com
157
+ const API_BASE_DEFAULT = "https://intimacy-compass.com";
158
+
159
+ const REQUIRED = {
160
+ version: "ic_map_v1",
161
+ topology_id: "ic_map_topology_v1",
162
+ nodeIds: [
163
+ "pace","directness","influence","stability_pref","precision_pref",
164
+ "priority_1","priority_2","priority_3","priority_4","priority_5",
165
+ "pursuit","withdrawal","control","appeasement","escalation"
166
+ ],
167
+ moveIds: ["pace","directness","influence","stability_pref","precision_pref"],
168
+ protectIds: ["priority_1","priority_2","priority_3","priority_4","priority_5"],
169
+ pressureIds: ["pursuit","withdrawal","control","appeasement","escalation"]
170
+ };
171
+
172
  // ====== Fixed trait nodes (exactly 15) ======
173
  const GROUPS = {
174
  move: { label: "How You Move", color: getCSS("--accent2") },
 
176
  pressure: { label: "Pressure Response", color: getCSS("--accent") }
177
  };
178
 
 
179
  const move = [
180
  { id: "pace", label: "Pace", group: "move" },
181
  { id: "directness", label: "Directness", group: "move" },
 
202
 
203
  const nodes = [...move, ...protect, ...pressure];
204
 
205
+ // Demo/default weights so renderer works without API.
206
+ nodes.forEach(n => {
207
+ n.weight_baseline = 1.8;
208
+ n.weight_pressure = 2.0;
209
+ });
210
+
211
  // Seed cluster positions (3 clusters in 3D space)
212
  seedCluster(move, { x: -80, y: 10, z: 40 });
213
  seedCluster(protect,{ x: 70, y: -10, z: -30 });
 
215
 
216
  // Links: dense within cluster + a few bridges between clusters (no new nodes)
217
  const links = [
218
+ ...ringLinksByIds(REQUIRED.moveIds),
219
+ ...ringLinksByIds(REQUIRED.protectIds),
220
+ ...ringLinksByIds(REQUIRED.pressureIds),
221
 
222
  // Bridges: minimal, purposeful
223
  { source: "directness", target: "priority_2" },
 
231
  const el = document.getElementById("graph");
232
  const hoverEl = document.getElementById("hover");
233
  const modeEl = document.getElementById("mode");
234
+ const dataEl = document.getElementById("data");
235
+
236
+ const legendMoveEl = document.getElementById("legend-move");
237
+ const legendProtectEl = document.getElementById("legend-protect");
238
+ const legendPressureEl = document.getElementById("legend-pressure");
239
 
240
  let isPressure = false;
241
  let hoveredNodeId = null;
 
290
  Graph.height(window.innerHeight);
291
  });
292
 
293
+ // ====== Live data wiring (Step #4) ======
294
+ (async function initLiveData() {
295
+ // Always render demo immediately, then try to upgrade to live.
296
+ renderLegends();
297
+ setDataStatus("Demo");
298
+
299
+ const params = getQueryParams();
300
+ if (!params.assessment_id || !params.viz_token) return;
301
+
302
+ const apiBase = params.api_base || API_BASE_DEFAULT;
303
+ const url = `${apiBase}/api/assessments/${encodeURIComponent(params.assessment_id)}/visualization?viz_token=${encodeURIComponent(params.viz_token)}`;
304
+
305
+ try {
306
+ const controller = new AbortController();
307
+ const timeout = setTimeout(() => controller.abort(), 8000);
308
+
309
+ const res = await fetch(url, {
310
+ method: "GET",
311
+ headers: { "Accept": "application/json" },
312
+ signal: controller.signal
313
+ });
314
+
315
+ clearTimeout(timeout);
316
+
317
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
318
+
319
+ const payload = await res.json();
320
+ if (!validatePayload(payload)) throw new Error("Invalid payload");
321
+
322
+ applyPayload(payload);
323
+ setDataStatus("Live");
324
+
325
+ // Re-bind the graph to ensure it re-renders with patched node fields.
326
+ Graph.graphData({ nodes, links })
327
+ .nodeLabel(n => n.label)
328
+ .nodeVal(n => nodeVal(n))
329
+ .nodeColor(n => nodeColor(n));
330
+
331
+ renderLegends();
332
+ } catch (e) {
333
+ // Fail open to demo
334
+ setDataStatus("Demo");
335
+ // no throw
336
+ }
337
+ })();
338
+
339
+ function getQueryParams() {
340
+ const sp = new URLSearchParams(window.location.search);
341
+ return {
342
+ assessment_id: sp.get("assessment_id"),
343
+ viz_token: sp.get("viz_token"),
344
+ api_base: sp.get("api_base")
345
+ };
346
+ }
347
+
348
+ function validatePayload(p) {
349
+ if (!p || typeof p !== "object") return false;
350
+ if (p.version !== REQUIRED.version) return false;
351
+ if (p.topology_id !== REQUIRED.topology_id) return false;
352
+ if (!Array.isArray(p.nodes) || p.nodes.length !== 15) return false;
353
+
354
+ const ids = new Set(p.nodes.map(n => n && n.id));
355
+ for (const reqId of REQUIRED.nodeIds) {
356
+ if (!ids.has(reqId)) return false;
357
+ }
358
+ return true;
359
+ }
360
+
361
+ function applyPayload(p) {
362
+ const map = new Map();
363
+ p.nodes.forEach(n => map.set(n.id, n));
364
+
365
+ nodes.forEach(n => {
366
+ const src = map.get(n.id);
367
+ if (!src) return;
368
+
369
+ // Labels: protect nodes dynamic, move+pressure fixed.
370
+ if (n.group === "protect" && typeof src.label === "string" && src.label.trim()) {
371
+ n.label = src.label.trim();
372
+ }
373
+
374
+ // Weights: always take from payload; HF must not invent weights.
375
+ n.weight_baseline = typeof src.weight_baseline === "number" ? src.weight_baseline : n.weight_baseline;
376
+ n.weight_pressure = typeof src.weight_pressure === "number" ? src.weight_pressure : n.weight_pressure;
377
+ });
378
+ }
379
+
380
+ function setDataStatus(kind) {
381
+ dataEl.textContent = "Data: " + (kind === "Live" ? "Live" : "Demo");
382
+ }
383
+
384
+ function renderLegends() {
385
+ // How You Move (fixed)
386
+ setLegendList(legendMoveEl, REQUIRED.moveIds.map(id => findNodeLabel(id)));
387
+
388
+ // What You Protect (dynamic)
389
+ setLegendList(legendProtectEl, REQUIRED.protectIds.map(id => findNodeLabel(id)));
390
+
391
+ // Pressure Response (fixed)
392
+ setLegendList(legendPressureEl, REQUIRED.pressureIds.map(id => findNodeLabel(id)));
393
+ }
394
+
395
+ function setLegendList(el, labels) {
396
+ el.innerHTML = "";
397
+ labels.forEach(txt => {
398
+ const li = document.createElement("li");
399
+ li.textContent = txt;
400
+ el.appendChild(li);
401
+ });
402
+ }
403
+
404
+ function findNodeLabel(id) {
405
+ const n = nodes.find(x => x.id === id);
406
+ return n ? n.label : id;
407
+ }
408
+
409
  // ====== Helpers ======
410
  function seedCluster(arr, center) {
411
  arr.forEach((n, i) => {
 
416
  });
417
  }
418
 
419
+ function ringLinksByIds(ids) {
420
  const out = [];
421
+ for (let i = 0; i < ids.length; i++) {
422
+ out.push({ source: ids[i], target: ids[(i + 1) % ids.length] });
423
+ out.push({ source: ids[i], target: ids[(i + 2) % ids.length] });
 
424
  }
425
  return out;
426
  }
427
 
428
  function nodeVal(n) {
 
 
 
 
 
 
429
  const hoverBoost = (hoveredNodeId && n.id === hoveredNodeId) ? 2.2 : 1.0;
430
+ const base = (typeof n.weight_baseline === "number") ? n.weight_baseline : 1.8;
431
+ const press = (typeof n.weight_pressure === "number") ? n.weight_pressure : 2.0;
432
+
433
+ if (!isPressure) return base;
434
+ return press * hoverBoost;
435
  }
436
 
437
  function nodeColor(n) {
438
  const base = GROUPS[n.group].color;
439
  if (!hoveredNodeId) return base;
 
 
440
  if (n.id === hoveredNodeId) return base;
441
  return "rgba(17,24,39,0.35)";
442
  }