+327
-117
drag-fix.js
+327
-117
drag-fix.js
···
1
-
// New unified drag system
2
-
console.log("Drag fix script loaded");
1
+
// Arc-based drag system for Go90 Scale
2
+
console.log("Arc drag system loaded");
3
+
4
+
// Arc configuration
5
+
const ARC_CONFIG = {
6
+
centerX: 0, // Will be set based on canvas width
7
+
centerY: 0, // Will be set based on canvas height
8
+
radius: 300,
9
+
startAngle: -Math.PI / 2, // -90° (top, 0 rating)
10
+
endAngle: 0, // 0° (right, 90 rating)
11
+
baseDistance: 50, // Base distance from arc for service icons
12
+
crowdingOffset: 120, // Distance to push out for each overlapping service
13
+
iconSize: 80,
14
+
};
15
+
16
+
let canvas, ctx, canvasContainer;
17
+
let servicePositions = {}; // Store { domain: { rating, angle, distance } }
18
+
let currentDrag = null;
3
19
4
20
function initDragAndDrop() {
5
-
console.log("initDragAndDrop called");
6
-
const canvas = document.getElementById("dragCanvas");
7
-
const servicesBar = document.getElementById("servicesBar");
8
-
const serviceItems = document.querySelectorAll(".service-item");
21
+
console.log("initDragAndDrop called - arc mode");
9
22
10
-
console.log("Found service items:", serviceItems.length);
23
+
canvasContainer = document.getElementById("dragCanvas");
24
+
canvas = document.getElementById("arcCanvas");
25
+
ctx = canvas.getContext("2d");
11
26
12
-
// Setup all service items for dragging
27
+
// Set canvas size
28
+
resizeCanvas();
29
+
window.addEventListener("resize", resizeCanvas);
30
+
31
+
// Draw the arc scale
32
+
drawArcScale();
33
+
34
+
// Setup service items for dragging
35
+
const serviceItems = document.querySelectorAll(".service-item");
13
36
serviceItems.forEach(setupServiceDrag);
37
+
}
14
38
15
-
// Make canvas accept drops
16
-
canvas.addEventListener("dragover", (e) => e.preventDefault());
17
-
canvas.addEventListener("drop", handleCanvasDrop);
39
+
function resizeCanvas() {
40
+
const rect = canvasContainer.getBoundingClientRect();
41
+
canvas.width = rect.width;
42
+
canvas.height = rect.height;
43
+
44
+
// Set arc center
45
+
ARC_CONFIG.centerX = canvas.width / 2;
46
+
ARC_CONFIG.centerY = canvas.height - 100;
47
+
48
+
drawArcScale();
49
+
redrawServices();
18
50
}
19
51
20
-
let currentDrag = null;
52
+
function drawArcScale() {
53
+
if (!ctx) return;
54
+
55
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
56
+
57
+
const { centerX, centerY, radius, startAngle, endAngle } = ARC_CONFIG;
58
+
59
+
// Draw gradient arc
60
+
const gradient = ctx.createLinearGradient(
61
+
centerX,
62
+
centerY + radius * Math.sin(startAngle),
63
+
centerX + radius * Math.cos(endAngle),
64
+
centerY + radius * Math.sin(endAngle),
65
+
);
66
+
67
+
gradient.addColorStop(0, "#32cd32");
68
+
gradient.addColorStop(0.2, "#adff2f");
69
+
gradient.addColorStop(0.4, "#ffff00");
70
+
gradient.addColorStop(0.6, "#ffa500");
71
+
gradient.addColorStop(0.8, "#ff4500");
72
+
gradient.addColorStop(1, "#8b0000");
73
+
74
+
ctx.strokeStyle = gradient;
75
+
ctx.lineWidth = 6;
76
+
ctx.beginPath();
77
+
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
78
+
ctx.stroke();
79
+
80
+
// Draw tick marks and labels
81
+
const totalRatings = 91; // 0 to 90
82
+
for (let rating = 0; rating <= 90; rating += 10) {
83
+
const angle = ratingToAngle(rating);
84
+
const tickLength = rating % 10 === 0 ? 20 : 10;
85
+
86
+
// Outer point on arc
87
+
const x1 = centerX + radius * Math.cos(angle);
88
+
const y1 = centerY + radius * Math.sin(angle);
89
+
90
+
// Inner point (tick mark)
91
+
const x2 = centerX + (radius - tickLength) * Math.cos(angle);
92
+
const y2 = centerY + (radius - tickLength) * Math.sin(angle);
93
+
94
+
ctx.strokeStyle = "white";
95
+
ctx.lineWidth = 3;
96
+
ctx.beginPath();
97
+
ctx.moveTo(x1, y1);
98
+
ctx.lineTo(x2, y2);
99
+
ctx.stroke();
100
+
101
+
// Draw rating label every 10
102
+
if (rating % 10 === 0) {
103
+
const labelDistance = radius - 50;
104
+
const labelX = centerX + labelDistance * Math.cos(angle);
105
+
const labelY = centerY + labelDistance * Math.sin(angle);
106
+
107
+
ctx.fillStyle = rating === 90 ? "#ff5555" : "white";
108
+
ctx.font = "bold 32px 'Innovator Grotesk', sans-serif";
109
+
ctx.textAlign = "center";
110
+
ctx.textBaseline = "middle";
111
+
ctx.fillText(rating.toString(), labelX, labelY);
112
+
}
113
+
}
114
+
115
+
// Draw indicator lines for placed services
116
+
Object.entries(servicePositions).forEach(([domain, data]) => {
117
+
drawIndicatorLine(data.angle, data.distance);
118
+
});
119
+
}
120
+
121
+
function drawIndicatorLine(angle, distance) {
122
+
const { centerX, centerY, radius } = ARC_CONFIG;
123
+
124
+
// Point on arc
125
+
const arcX = centerX + radius * Math.cos(angle);
126
+
const arcY = centerY + radius * Math.sin(angle);
127
+
128
+
// Point at service icon
129
+
const iconX = centerX + distance * Math.cos(angle);
130
+
const iconY = centerY + distance * Math.sin(angle);
131
+
132
+
ctx.strokeStyle = "white";
133
+
ctx.lineWidth = 2;
134
+
ctx.setLineDash([5, 5]);
135
+
ctx.beginPath();
136
+
ctx.moveTo(arcX, arcY);
137
+
ctx.lineTo(iconX, iconY);
138
+
ctx.stroke();
139
+
ctx.setLineDash([]);
140
+
}
141
+
142
+
function ratingToAngle(rating) {
143
+
const { startAngle, endAngle } = ARC_CONFIG;
144
+
const t = rating / 90;
145
+
return startAngle + t * (endAngle - startAngle);
146
+
}
147
+
148
+
function angleToRating(angle) {
149
+
const { startAngle, endAngle } = ARC_CONFIG;
150
+
const t = (angle - startAngle) / (endAngle - startAngle);
151
+
return Math.max(0, Math.min(90, Math.round(t * 90)));
152
+
}
21
153
22
154
function setupServiceDrag(element) {
23
155
element.style.cursor = "grab";
···
25
157
}
26
158
27
159
function startDrag(e) {
28
-
const serviceItem = e.target.closest(".service-item");
160
+
const serviceItem = e.target.closest(".service-item, .service-on-canvas");
29
161
if (!serviceItem) return;
30
162
31
163
e.preventDefault();
32
164
33
-
const clone = serviceItem.cloneNode(true);
34
-
clone.style.position = "fixed";
35
-
clone.style.pointerEvents = "none";
36
-
clone.style.zIndex = "10000";
37
-
clone.style.width = "80px";
38
-
clone.style.height = "80px";
165
+
const domain = serviceItem.dataset.domain || serviceItem.dataset.canvasDomain;
166
+
const name = serviceItem.dataset.name;
39
167
40
168
currentDrag = {
169
+
domain,
170
+
name,
41
171
element: serviceItem,
42
-
clone: clone,
43
-
domain: serviceItem.dataset.domain,
44
-
name: serviceItem.dataset.name,
45
-
startX: e.clientX,
46
-
startY: e.clientY,
172
+
isFromCanvas: serviceItem.classList.contains("service-on-canvas"),
47
173
};
48
174
49
-
document.body.appendChild(clone);
50
-
updateClonePosition(e);
51
-
52
-
serviceItem.style.opacity = "0.3";
53
-
175
+
serviceItem.style.opacity = "0.5";
54
176
document.addEventListener("mousemove", onDragMove);
55
177
document.addEventListener("mouseup", onDragEnd);
56
178
}
57
179
58
180
function onDragMove(e) {
59
181
if (!currentDrag) return;
60
-
updateClonePosition(e);
61
-
}
182
+
183
+
// Update visual feedback based on mouse position relative to arc
184
+
const canvasRect = canvas.getBoundingClientRect();
185
+
const mouseX = e.clientX - canvasRect.left;
186
+
const mouseY = e.clientY - canvasRect.top;
187
+
188
+
// Calculate angle from center to mouse
189
+
const dx = mouseX - ARC_CONFIG.centerX;
190
+
const dy = mouseY - ARC_CONFIG.centerY;
191
+
const angle = Math.atan2(dy, dx);
192
+
193
+
// Constrain to arc range
194
+
const { startAngle, endAngle } = ARC_CONFIG;
195
+
const constrainedAngle = Math.max(startAngle, Math.min(endAngle, angle));
196
+
197
+
// Show preview by updating element position if it's on canvas
198
+
if (currentDrag.isFromCanvas) {
199
+
const distance = calculateDistance(currentDrag.domain, constrainedAngle);
200
+
const x = ARC_CONFIG.centerX + distance * Math.cos(constrainedAngle);
201
+
const y = ARC_CONFIG.centerY + distance * Math.sin(constrainedAngle);
62
202
63
-
function updateClonePosition(e) {
64
-
if (!currentDrag) return;
65
-
currentDrag.clone.style.left = e.clientX - 40 + "px";
66
-
currentDrag.clone.style.top = e.clientY - 40 + "px";
203
+
currentDrag.element.style.left = x - ARC_CONFIG.iconSize / 2 + "px";
204
+
currentDrag.element.style.top = y - ARC_CONFIG.iconSize / 2 + "px";
205
+
}
67
206
}
68
207
69
208
function onDragEnd(e) {
···
72
211
document.removeEventListener("mousemove", onDragMove);
73
212
document.removeEventListener("mouseup", onDragEnd);
74
213
75
-
const domain = currentDrag.domain;
76
-
const name = currentDrag.name;
214
+
const { domain, name, element, isFromCanvas } = currentDrag;
77
215
78
-
// Check where it was dropped
79
-
const canvas = document.getElementById("dragCanvas");
216
+
// Check if dropped on services bar (to remove rating)
80
217
const servicesBar = document.getElementById("servicesBar");
81
-
const scaleBar = document.getElementById("scaleBar");
82
-
83
218
const servicesBarRect = servicesBar.getBoundingClientRect();
84
-
const canvasRect = canvas.getBoundingClientRect();
85
-
const scaleBarRect = scaleBar.getBoundingClientRect();
86
-
87
-
const dropX = e.clientX;
88
-
const dropY = e.clientY;
89
219
90
-
// Clean up
91
-
currentDrag.clone.remove();
92
-
currentDrag.element.style.opacity = "1";
93
-
94
-
// Check if dropped on services bar
95
220
if (
96
-
dropY >= servicesBarRect.top &&
97
-
dropY <= servicesBarRect.bottom &&
98
-
dropX >= servicesBarRect.left &&
99
-
dropX <= servicesBarRect.right
221
+
e.clientY >= servicesBarRect.top &&
222
+
e.clientY <= servicesBarRect.bottom &&
223
+
e.clientX >= servicesBarRect.left &&
224
+
e.clientX <= servicesBarRect.right
100
225
) {
101
-
// Return to bar - remove rating
102
-
removeServiceFromCanvas(domain);
103
-
delete pendingRatings[domain];
104
-
updateSaveButton();
226
+
// Remove from canvas and add back to bar
227
+
removeServiceFromCanvas(domain, name);
228
+
element.style.opacity = "1";
105
229
currentDrag = null;
106
230
return;
107
231
}
108
232
109
-
// Calculate rating from horizontal position relative to scale bar
110
-
const scaleX = dropX - scaleBarRect.left;
111
-
const percentage = Math.max(0, Math.min(100, (scaleX / scaleBarRect.width) * 100));
112
-
const rating = Math.round((percentage / 100) * 90);
233
+
// Calculate position on arc
234
+
const canvasRect = canvas.getBoundingClientRect();
235
+
const mouseX = e.clientX - canvasRect.left;
236
+
const mouseY = e.clientY - canvasRect.top;
113
237
114
-
// Position on canvas
115
-
const canvasX = dropX - canvasRect.left;
116
-
const canvasY = dropY - canvasRect.top;
238
+
const dx = mouseX - ARC_CONFIG.centerX;
239
+
const dy = mouseY - ARC_CONFIG.centerY;
240
+
const angle = Math.atan2(dy, dx);
117
241
118
-
// Store pending rating
119
-
pendingRatings[domain] = {
120
-
domain,
121
-
name,
122
-
rating,
123
-
x: canvasX,
124
-
y: canvasY,
125
-
};
242
+
// Constrain to arc range
243
+
const { startAngle, endAngle } = ARC_CONFIG;
244
+
const constrainedAngle = Math.max(startAngle, Math.min(endAngle, angle));
245
+
246
+
const rating = angleToRating(constrainedAngle);
126
247
127
248
// Place on canvas
128
-
placeServiceOnCanvas(domain, name, rating, canvasX, canvasY);
129
-
updateSaveButton();
249
+
placeServiceOnArc(domain, name, rating);
130
250
251
+
element.style.opacity = "1";
131
252
currentDrag = null;
132
253
}
133
254
134
-
function placeServiceOnCanvas(domain, name, rating, x, y) {
135
-
const canvas = document.getElementById("dragCanvas");
136
-
const servicesBar = document.getElementById("servicesBar");
255
+
function calculateDistance(domain, angle) {
256
+
// Find the furthest "valence ring" occupied within 5 degrees BEFORE this angle
257
+
// This prevents recursive offsetting - only look backwards along the arc
258
+
// Only services at the current ring level count as crowding
259
+
const angularTolerance = 10 * (Math.PI / 180); // 5 degrees in radians
260
+
const baseDistance = ARC_CONFIG.radius + ARC_CONFIG.baseDistance;
261
+
262
+
// Find all occupied rings in the angular range
263
+
let occupiedRings = new Set();
264
+
Object.entries(servicePositions).forEach(([d, data]) => {
265
+
if (d !== domain) {
266
+
const angleDiff = angle - data.angle; // Positive if data.angle is before this angle
267
+
if (angleDiff >= 0 && angleDiff <= angularTolerance) {
268
+
// Found a service in the 5 degrees before
269
+
occupiedRings.add(data.distance);
270
+
}
271
+
}
272
+
});
273
+
274
+
// Find the first unoccupied ring
275
+
let testDistance = baseDistance;
276
+
while (occupiedRings.has(testDistance)) {
277
+
testDistance += ARC_CONFIG.crowdingOffset;
278
+
}
279
+
280
+
return testDistance;
281
+
}
282
+
283
+
function placeServiceOnArc(domain, name, rating) {
284
+
const angle = ratingToAngle(rating);
285
+
const distance = calculateDistance(domain, angle);
286
+
287
+
// Store position
288
+
servicePositions[domain] = { rating, angle, distance };
137
289
138
-
// Remove from services bar if it's there
290
+
// Store in pending ratings
291
+
if (typeof pendingRatings !== "undefined") {
292
+
pendingRatings[domain] = { domain, name, rating };
293
+
}
294
+
295
+
// Remove from services bar
296
+
const servicesBar = document.getElementById("servicesBar");
139
297
const inBar = servicesBar.querySelector(`[data-domain="${domain}"]`);
140
-
if (inBar) {
141
-
inBar.remove();
142
-
}
298
+
if (inBar) inBar.remove();
143
299
144
-
// Remove existing placement on canvas
145
-
const existing = canvas.querySelector(`[data-canvas-domain="${domain}"]`);
300
+
// Remove existing on canvas
301
+
const existing = canvasContainer.querySelector(`[data-canvas-domain="${domain}"]`);
146
302
if (existing) existing.remove();
147
303
304
+
// Calculate pixel position
305
+
const x = ARC_CONFIG.centerX + distance * Math.cos(angle);
306
+
const y = ARC_CONFIG.centerY + distance * Math.sin(angle);
307
+
148
308
// Create element
149
309
const el = document.createElement("div");
150
-
el.className = "service-item on-canvas";
310
+
el.className = "service-on-canvas";
311
+
el.dataset.canvasDomain = domain;
151
312
el.dataset.domain = domain;
152
313
el.dataset.name = name;
153
-
el.dataset.canvasDomain = domain;
154
-
el.style.position = "absolute";
155
-
el.style.left = x - 40 + "px";
156
-
el.style.top = y - 40 + "px";
314
+
el.style.left = x - ARC_CONFIG.iconSize / 2 + "px";
315
+
el.style.top = y - ARC_CONFIG.iconSize / 2 + "px";
157
316
el.innerHTML = `
158
317
<img src="https://www.google.com/s2/favicons?domain=${domain}&sz=128"
159
318
alt="${name}"
160
-
class="service-logo"
319
+
class="service-icon"
161
320
draggable="false">
162
321
<div class="service-badge ${rating === 90 ? "defunct" : ""}">${rating}</div>
163
322
`;
164
323
165
324
setupServiceDrag(el);
166
-
canvas.appendChild(el);
325
+
canvasContainer.appendChild(el);
326
+
327
+
// Redraw canvas to show indicator line
328
+
drawArcScale();
329
+
330
+
// Reposition any crowded services
331
+
repositionCrowdedServices();
332
+
333
+
if (typeof updateSaveButton !== "undefined") {
334
+
updateSaveButton();
335
+
}
167
336
}
168
337
169
-
function removeServiceFromCanvas(domain) {
170
-
const canvas = document.getElementById("dragCanvas");
338
+
function repositionCrowdedServices() {
339
+
// Recalculate distances for all services to handle crowding
340
+
Object.entries(servicePositions).forEach(([domain, data]) => {
341
+
const newDistance = calculateDistance(domain, data.angle);
342
+
if (newDistance !== data.distance) {
343
+
data.distance = newDistance;
344
+
345
+
// Update element position
346
+
const el = canvasContainer.querySelector(`[data-canvas-domain="${domain}"]`);
347
+
if (el) {
348
+
const x = ARC_CONFIG.centerX + newDistance * Math.cos(data.angle);
349
+
const y = ARC_CONFIG.centerY + newDistance * Math.sin(data.angle);
350
+
el.style.left = x - ARC_CONFIG.iconSize / 2 + "px";
351
+
el.style.top = y - ARC_CONFIG.iconSize / 2 + "px";
352
+
}
353
+
}
354
+
});
355
+
356
+
drawArcScale();
357
+
}
358
+
359
+
function removeServiceFromCanvas(domain, name) {
360
+
// Remove from positions
361
+
delete servicePositions[domain];
362
+
363
+
// Remove from canvas
364
+
const existing = canvasContainer.querySelector(`[data-canvas-domain="${domain}"]`);
365
+
if (existing) existing.remove();
366
+
367
+
// Add back to services bar
171
368
const servicesBar = document.getElementById("servicesBar");
369
+
const rating = window.existingRatings ? window.existingRatings[domain] : undefined;
172
370
173
-
// Remove from canvas
174
-
const existing = canvas.querySelector(`[data-canvas-domain="${domain}"]`);
175
-
if (existing) {
176
-
const name = existing.dataset.name;
177
-
const rating = window.existingRatings ? window.existingRatings[domain] : undefined;
178
-
existing.remove();
371
+
const serviceEl = document.createElement("div");
372
+
serviceEl.className = "service-item";
373
+
serviceEl.dataset.domain = domain;
374
+
serviceEl.dataset.name = name;
375
+
serviceEl.innerHTML = `
376
+
<img src="https://www.google.com/s2/favicons?domain=${domain}&sz=128"
377
+
alt="${name}"
378
+
class="service-logo"
379
+
draggable="false">
380
+
${rating !== undefined ? `<div class="service-badge ${rating === 90 ? "defunct" : ""}">${rating}</div>` : ""}
381
+
`;
382
+
setupServiceDrag(serviceEl);
383
+
servicesBar.appendChild(serviceEl);
179
384
180
-
// Add back to services bar
181
-
const serviceEl = document.createElement("div");
182
-
serviceEl.className = "service-item";
183
-
serviceEl.dataset.domain = domain;
184
-
serviceEl.dataset.name = name;
185
-
serviceEl.innerHTML = `
186
-
<img src="https://www.google.com/s2/favicons?domain=${domain}&sz=128"
187
-
alt="${name}"
188
-
class="service-logo"
189
-
draggable="false">
190
-
${rating !== undefined ? `<div class="service-badge ${rating === 90 ? "defunct" : ""}">${rating}</div>` : ""}
191
-
`;
192
-
setupServiceDrag(serviceEl);
193
-
servicesBar.appendChild(serviceEl);
385
+
// Remove from pending
386
+
if (typeof pendingRatings !== "undefined") {
387
+
delete pendingRatings[domain];
388
+
}
389
+
390
+
// Redraw and reposition
391
+
drawArcScale();
392
+
repositionCrowdedServices();
393
+
394
+
if (typeof updateSaveButton !== "undefined") {
395
+
updateSaveButton();
194
396
}
195
397
}
196
398
197
-
function handleCanvasDrop(e) {
198
-
// This is here for compatibility but we use mouse events instead
199
-
e.preventDefault();
399
+
function redrawServices() {
400
+
// Redraw all services at their current positions
401
+
Object.entries(servicePositions).forEach(([domain, data]) => {
402
+
const el = canvasContainer.querySelector(`[data-canvas-domain="${domain}"]`);
403
+
if (el) {
404
+
const x = ARC_CONFIG.centerX + data.distance * Math.cos(data.angle);
405
+
const y = ARC_CONFIG.centerY + data.distance * Math.sin(data.angle);
406
+
el.style.left = x - ARC_CONFIG.iconSize / 2 + "px";
407
+
el.style.top = y - ARC_CONFIG.iconSize / 2 + "px";
408
+
}
409
+
});
200
410
}
+66
-120
index.html
+66
-120
index.html
···
399
399
/* Go90 Scale Interface */
400
400
.scale-container {
401
401
background: var(--go90-blue);
402
-
border: 4px solid var(--go90-yellow);
403
-
padding: 3rem 2rem;
402
+
padding: 2rem;
404
403
border-radius: 8px;
405
404
margin-bottom: 2rem;
406
-
min-height: 600px;
407
405
position: relative;
406
+
display: flex;
407
+
flex-direction: column;
408
+
align-items: center;
408
409
}
409
410
410
411
.drag-canvas {
411
412
position: relative;
412
-
min-height: 500px;
413
-
}
414
-
415
-
.scale-bar-wrapper {
416
-
margin-bottom: 3rem;
413
+
width: 100%;
414
+
height: 700px;
415
+
margin-bottom: 2rem;
417
416
}
418
417
419
-
.scale-labels {
420
-
display: flex;
421
-
justify-content: space-between;
422
-
margin-bottom: 1rem;
423
-
}
424
-
425
-
.scale-label {
426
-
font-size: 3rem;
427
-
font-weight: 900;
428
-
color: var(--go90-yellow);
429
-
}
430
-
431
-
.scale-label.red {
432
-
color: #ff5555;
433
-
}
434
-
435
-
.scale-bar {
436
-
position: relative;
437
-
height: 80px;
438
-
background: linear-gradient(
439
-
to right,
440
-
#32cd32 0%,
441
-
#7fff00 10%,
442
-
#adff2f 20%,
443
-
#ffff00 30%,
444
-
#ffd700 40%,
445
-
#ffa500 50%,
446
-
#ff8c00 60%,
447
-
#ff6347 70%,
448
-
#ff4500 80%,
449
-
#dc143c 90%,
450
-
#8b0000 100%
451
-
);
452
-
border-radius: 12px;
453
-
border: 3px solid white;
454
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
455
-
margin-bottom: 120px;
456
-
margin-top: 120px;
457
-
}
458
-
459
-
.scale-bar.drag-over {
460
-
box-shadow: 0 0 20px rgba(255, 255, 0, 0.8);
461
-
border-color: var(--go90-yellow);
462
-
}
463
-
464
-
.drop-zone {
418
+
#arcCanvas {
465
419
position: absolute;
466
420
top: 0;
467
421
left: 0;
468
-
right: 0;
469
-
bottom: 0;
470
-
border-radius: 12px;
422
+
pointer-events: none;
471
423
}
472
424
473
-
.service-on-scale {
425
+
.service-on-canvas {
474
426
position: absolute;
475
-
top: 50%;
476
-
transform: translate(-50%, -50%);
477
-
cursor: move;
427
+
cursor: grab;
478
428
transition: transform 0.1s;
479
429
}
480
430
481
-
.service-on-scale:hover {
482
-
transform: translate(-50%, -50%) scale(1.1);
431
+
.service-on-canvas:active {
432
+
cursor: grabbing;
483
433
}
484
434
485
-
.service-logo-large {
435
+
.service-on-canvas:hover {
436
+
transform: scale(1.05);
437
+
}
438
+
439
+
.service-icon {
486
440
width: 80px;
487
441
height: 80px;
488
442
border-radius: 12px;
489
443
background: white;
490
444
padding: 8px;
491
445
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
492
-
border: 3px solid white;
446
+
border: 3px solid var(--go90-yellow);
447
+
display: block;
493
448
}
494
449
495
450
.service-badge {
···
513
468
}
514
469
515
470
.services-bar {
516
-
background: black;
471
+
background: transparent;
517
472
padding: 1.5rem;
518
473
border-radius: 8px;
519
474
display: flex;
520
475
gap: 1rem;
521
476
align-items: center;
477
+
justify-content: center;
522
478
flex-wrap: wrap;
523
-
min-height: 100px;
479
+
border: 3px solid rgba(255, 255, 255, 0.3);
524
480
}
525
481
526
482
.service-item {
527
483
width: 80px;
528
484
height: 80px;
529
485
border-radius: 8px;
530
-
background: white;
486
+
background: black;
531
487
padding: 8px;
532
488
cursor: grab;
533
489
transition:
···
565
521
display: flex;
566
522
gap: 0.5rem;
567
523
align-items: center;
568
-
margin-top: 1rem;
524
+
justify-content: center;
525
+
width: 100%;
526
+
max-width: 600px;
569
527
}
570
528
571
529
.add-service-input {
572
530
flex: 1;
573
-
padding: 0.75rem;
574
-
border: 2px solid var(--gray-500);
575
-
border-radius: 4px;
576
-
background: var(--gray-900);
531
+
padding: 1rem 1.5rem;
532
+
border: 3px solid rgba(255, 255, 255, 0.3);
533
+
border-radius: 8px;
534
+
background: transparent;
577
535
color: white;
578
-
font-size: 1rem;
536
+
font-size: 1.25rem;
537
+
font-weight: 500;
538
+
}
539
+
540
+
.add-service-input::placeholder {
541
+
color: rgba(255, 255, 255, 0.5);
579
542
}
580
543
581
544
.add-service-input:focus {
582
545
outline: none;
583
-
border-color: var(--go90-yellow);
546
+
border-color: white;
584
547
}
585
548
586
549
.add-service-btn {
587
-
padding: 0.75rem 1.5rem;
588
-
background: var(--go90-yellow);
589
-
color: black;
590
-
border: none;
591
-
border-radius: 4px;
550
+
padding: 1rem 1.5rem;
551
+
background: transparent;
552
+
color: white;
553
+
border: 3px solid rgba(255, 255, 255, 0.3);
554
+
border-radius: 8px;
592
555
font-weight: 700;
593
556
cursor: pointer;
594
-
font-size: 1rem;
557
+
font-size: 1.25rem;
558
+
transition: all 0.2s;
595
559
}
596
560
597
561
.add-service-btn:hover {
598
-
background: #ffff44;
562
+
border-color: white;
563
+
background: rgba(255, 255, 255, 0.1);
599
564
}
600
565
601
566
.instructions {
···
1219
1184
const container = document.getElementById("content");
1220
1185
1221
1186
container.innerHTML = `
1222
-
<div class="instructions">
1223
-
Drag services anywhere to rate them! Drop on the bar to remove rating.
1224
-
</div>
1225
-
1226
-
<div class="scale-container drag-canvas" id="dragCanvas">
1227
-
<div class="scale-bar-wrapper">
1228
-
<div class="scale-labels">
1229
-
<span class="scale-label">0</span>
1230
-
<span class="scale-label red">90</span>
1231
-
</div>
1232
-
<div class="scale-bar" id="scaleBar"></div>
1187
+
<div class="scale-container">
1188
+
<div class="drag-canvas" id="dragCanvas">
1189
+
<canvas id="arcCanvas"></canvas>
1233
1190
</div>
1234
1191
1235
1192
<div class="services-bar" id="servicesBar">
···
1253
1210
<input type="text"
1254
1211
id="customServiceDomain"
1255
1212
class="add-service-input"
1256
-
placeholder="Enter a domain (e.g., dropout.tv)">
1257
-
<button type="submit" class="add-service-btn">Add Service</button>
1213
+
placeholder="add service">
1214
+
<button type="submit" class="add-service-btn">add service</button>
1258
1215
</form>
1259
1216
</div>
1260
1217
`;
···
1310
1267
const data = await client.query(ratingsQuery);
1311
1268
const allEdges = data?.socialGo90Rating?.edges || [];
1312
1269
1313
-
// Filter to only viewer's ratings
1314
-
const myRatings = allEdges
1270
+
// Filter to only viewer's ratings and deduplicate by domain (keep latest)
1271
+
const ratingsByDomain = {};
1272
+
allEdges
1315
1273
.filter((edge) => edge.node.did === viewerDid)
1316
-
.map((edge) => edge.node);
1274
+
.forEach((edge) => {
1275
+
const node = edge.node;
1276
+
ratingsByDomain[node.serviceDomain] = node;
1277
+
});
1278
+
const myRatings = Object.values(ratingsByDomain);
1317
1279
1318
1280
if (myRatings && myRatings.length > 0) {
1319
1281
// Store ratings in global object
···
1324
1286
// Update badges on services in the bar
1325
1287
updateServiceBadges();
1326
1288
1327
-
const scaleBar = document.getElementById("scaleBar");
1328
-
const scaleBarRect = scaleBar.getBoundingClientRect();
1329
-
const canvas = document.getElementById("dragCanvas");
1330
-
const canvasRect = canvas.getBoundingClientRect();
1331
-
1332
-
// Load viewer's ratings onto canvas
1289
+
// Load viewer's ratings onto arc canvas
1333
1290
for (const rating of myRatings) {
1334
-
// Calculate position based on rating value
1335
-
const percentageX = (rating.rating / 90) * 100;
1336
-
const scaleX = scaleBarRect.left + (percentageX / 100) * scaleBarRect.width;
1337
-
const scaleY = scaleBarRect.top + scaleBarRect.height / 2;
1338
-
1339
-
// Convert to canvas coordinates
1340
-
const canvasX = scaleX - canvasRect.left;
1341
-
const canvasY = scaleY - canvasRect.top;
1291
+
console.log(`Loading ${rating.serviceDomain} at rating ${rating.rating}`);
1342
1292
1343
-
// Place on canvas
1344
-
placeServiceOnCanvas(
1345
-
rating.serviceDomain,
1346
-
rating.serviceDomain,
1347
-
rating.rating,
1348
-
canvasX,
1349
-
canvasY,
1350
-
);
1293
+
// Use arc-based placement from drag-fix.js
1294
+
if (typeof placeServiceOnArc !== "undefined") {
1295
+
placeServiceOnArc(rating.serviceDomain, rating.serviceDomain, rating.rating);
1296
+
}
1351
1297
}
1352
1298
}
1353
1299
} catch (error) {