Skip to content

Commit 394964d

Browse files
committed
add : UX/UI feature, colorblind mode and fix some bugs
1 parent dde85ed commit 394964d

10 files changed

Lines changed: 540 additions & 34 deletions

File tree

website/assets/css/style.css

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,16 @@ strong {
235235
display: none;
236236
}
237237

238+
.nav-cb[aria-pressed="true"] {
239+
border-color: var(--accent);
240+
background: var(--bg-panel);
241+
color: var(--ink);
242+
}
243+
244+
.nav-cb[aria-pressed="true"] .nav-num {
245+
color: var(--accent);
246+
}
247+
238248
/* Views */
239249
.view {
240250
display: none;
@@ -308,6 +318,14 @@ strong {
308318
color: var(--ink);
309319
}
310320

321+
.view-hint {
322+
display: inline-block;
323+
font-family: var(--mono);
324+
font-size: 11px;
325+
color: var(--ink-faint);
326+
margin-top: 6px;
327+
}
328+
311329
/* View 1 */
312330

313331
.view-main {
@@ -898,6 +916,20 @@ input[type="range"]::-moz-range-thumb {
898916
max-width: 240px;
899917
}
900918

919+
.axis-picker-reset {
920+
flex: 0 0 auto;
921+
max-width: none;
922+
display: flex;
923+
flex-direction: column;
924+
justify-content: flex-end;
925+
}
926+
927+
.axis-picker-reset .btn-ghost {
928+
padding: 10px 16px;
929+
font-size: 12px;
930+
white-space: nowrap;
931+
}
932+
901933
.axis-picker label {
902934
display: block;
903935
font-family: var(--mono);
@@ -1059,6 +1091,88 @@ input[type="range"]::-moz-range-thumb {
10591091
color: var(--dying);
10601092
}
10611093

1094+
.sel-chip.chip-marked {
1095+
outline: 2px solid var(--dying);
1096+
background: rgba(217, 108, 108, 0.12);
1097+
}
1098+
1099+
.btn-danger {
1100+
color: var(--dying) !important;
1101+
border-color: var(--dying) !important;
1102+
}
1103+
1104+
/* Bookmarks */
1105+
.sandbox-bookmarks {
1106+
padding: 16px 0 0;
1107+
border-top: 1px solid var(--rule);
1108+
margin-top: 16px;
1109+
}
1110+
1111+
.bookmarks-row {
1112+
display: flex;
1113+
gap: 8px;
1114+
margin-bottom: 10px;
1115+
}
1116+
1117+
.bookmark-input {
1118+
flex: 1;
1119+
background: var(--bg);
1120+
border: 1px solid var(--rule);
1121+
color: var(--ink);
1122+
font-family: var(--mono);
1123+
font-size: 11px;
1124+
padding: 6px 10px;
1125+
border-radius: 2px;
1126+
outline: none;
1127+
}
1128+
1129+
.bookmark-input:focus {
1130+
border-color: var(--accent);
1131+
}
1132+
1133+
.bookmark-chips {
1134+
display: flex;
1135+
flex-wrap: wrap;
1136+
gap: 6px;
1137+
}
1138+
1139+
.bookmark-chip {
1140+
display: inline-flex;
1141+
align-items: center;
1142+
gap: 4px;
1143+
background: #000;
1144+
border: 1px solid #000;
1145+
border-radius: 2px;
1146+
font-family: var(--mono);
1147+
font-size: 10px;
1148+
color: #fff;
1149+
}
1150+
1151+
.bookmark-name {
1152+
padding: 5px 8px;
1153+
cursor: pointer;
1154+
color: #fff;
1155+
}
1156+
1157+
.bookmark-name:hover {
1158+
color: var(--accent);
1159+
}
1160+
1161+
.bookmark-remove {
1162+
background: none;
1163+
border: none;
1164+
color: var(--ink-faint);
1165+
cursor: pointer;
1166+
font-size: 14px;
1167+
padding: 0 6px;
1168+
line-height: 1;
1169+
transition: color 0.15s;
1170+
}
1171+
1172+
.bookmark-remove:hover {
1173+
color: var(--dying);
1174+
}
1175+
10621176
/* Sandbox time series */
10631177
.sandbox-timeseries {
10641178
margin-top: 32px;

website/assets/js/analysis.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ function buildDecayCurve(games) {
8383
.forEach((g) => {
8484
const relDate = new Date(g.year, g.release_month, 1);
8585

86-
// find this game's player count at month 6 post-release to use as baseline
8786
let baselineAt6 = null;
8887
g.series.forEach(({ month, peak: monthPeak }) => {
8988
const obsDate = new Date(month);

website/assets/js/bookmarks.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// ==========================================================
2+
// BOOKMARKS - save/restore named sandbox views in localStorage
3+
// ==========================================================
4+
5+
const BOOKMARKS_KEY = "mobava_bookmarks_v1";
6+
7+
function listBookmarks() {
8+
try {
9+
return JSON.parse(localStorage.getItem(BOOKMARKS_KEY) || "[]");
10+
} catch {
11+
return [];
12+
}
13+
}
14+
15+
function saveBookmark(name) {
16+
if (!name.trim()) return;
17+
const bookmarks = listBookmarks();
18+
bookmarks.push({
19+
id: Date.now().toString(36) + Math.random().toString(36).slice(2),
20+
name: name.trim(),
21+
selectedIds: [...state.selectedIds],
22+
x: sandbox.xField,
23+
y: sandbox.yField,
24+
color: sandbox.colorField,
25+
size: sandbox.sizeField,
26+
createdAt: new Date().toISOString(),
27+
});
28+
localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(bookmarks));
29+
renderBookmarks();
30+
}
31+
32+
function applyBookmark(id) {
33+
const bm = listBookmarks().find((b) => b.id === id);
34+
if (!bm) return;
35+
state.selectedIds.clear();
36+
bm.selectedIds.forEach((sid) => {
37+
if (GAMES_DATA.find((g) => g.id === sid)) state.selectedIds.add(sid);
38+
});
39+
sandbox.xField = bm.x;
40+
sandbox.yField = bm.y;
41+
sandbox.colorField = bm.color;
42+
sandbox.sizeField = bm.size || "none";
43+
d3.select("#sandbox-x").property("value", bm.x);
44+
d3.select("#sandbox-y").property("value", bm.y);
45+
d3.select("#sandbox-color").property("value", bm.color);
46+
d3.select("#sandbox-size").property("value", bm.size || "none");
47+
updateSelectionUI();
48+
if (sandbox.initialized) renderSandbox();
49+
updateSandboxDescriptions();
50+
}
51+
52+
function deleteBookmark(id) {
53+
const updated = listBookmarks().filter((b) => b.id !== id);
54+
localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(updated));
55+
renderBookmarks();
56+
}
57+
58+
function renderBookmarks() {
59+
const bookmarks = listBookmarks();
60+
const container = document.getElementById("bookmark-chips");
61+
if (!container) return;
62+
63+
d3.select(container)
64+
.selectAll(".bookmark-chip")
65+
.data(bookmarks, (d) => d.id)
66+
.join(
67+
(enter) => {
68+
const chip = enter.append("div").attr("class", "bookmark-chip");
69+
chip
70+
.append("span")
71+
.attr("class", "bookmark-name")
72+
.on("click", (_, d) => applyBookmark(d.id));
73+
chip
74+
.append("button")
75+
.attr("class", "bookmark-remove")
76+
.text("×")
77+
.on("click", (_, d) => deleteBookmark(d.id));
78+
return chip;
79+
},
80+
(update) => update,
81+
(exit) => exit.remove(),
82+
)
83+
.select(".bookmark-name")
84+
.text((d) => d.name);
85+
}

website/assets/js/chart-toolbar.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ const _OVERLAY_ITEMS = [
1010
{ sel: "#selection-tray", fsClass: "is-fullscreen-tray" },
1111
];
1212

13+
const _ICON_DOWNLOAD = `<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
14+
<line x1="8" y1="2" x2="8" y2="10"/>
15+
<polyline points="5,7 8,10 11,7"/>
16+
<polyline points="2,13 2,14 14,14 14,13"/>
17+
</svg>`;
18+
1319
const _ICON_EXPAND = `<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
1420
<polyline points="6,2 2,2 2,6"/>
1521
<polyline points="10,2 14,2 14,6"/>
@@ -30,6 +36,7 @@ function setupChartToolbar({
3036
chartObj,
3137
reinit,
3238
hasOverlayPanel = false,
39+
onExport = null,
3340
}) {
3441
// remove any existing toolbar
3542
const existing = wrapEl.querySelector(".chart-toolbar");
@@ -38,12 +45,20 @@ function setupChartToolbar({
3845
const toolbar = document.createElement("div");
3946
toolbar.className = "chart-toolbar";
4047
toolbar.innerHTML = `
48+
${onExport ? `<button class="ct-btn ct-export" title="Export PNG">${_ICON_DOWNLOAD}</button>` : ""}
4149
<button class="ct-btn ct-fullscreen" title="Fullscreen">${_ICON_EXPAND}</button>
4250
<button class="ct-btn ct-zoom-in" title="Zoom in">+</button>
4351
<button class="ct-btn ct-zoom-out" title="Zoom out">-</button>
4452
`;
4553
wrapEl.appendChild(toolbar);
4654

55+
if (onExport) {
56+
toolbar.querySelector(".ct-export").addEventListener("click", (e) => {
57+
e.stopPropagation();
58+
onExport();
59+
});
60+
}
61+
4762
_toolbarRegistry.set(wrapEl, { svgEl, chartObj, reinit, hasOverlayPanel });
4863

4964
toolbar.querySelector(".ct-zoom-in").addEventListener("click", (e) => {

website/assets/js/constants.js

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,54 @@ const ARCHETYPE_LABELS = {
4545
mid: "Mid",
4646
};
4747

48-
const ARCHETYPE_COLOR = {
48+
let ARCHETYPE_COLOR = {
4949
immortal: "#7fc97f",
5050
aaa: "#e6a356",
5151
slow_burn: "#8ab4ff",
5252
fading_aaa: "#d96c6c",
5353
mid: "#a69a8c",
5454
};
5555

56-
const TS_PALETTE = d3.schemeTableau10;
56+
let TS_PALETTE = d3.schemeTableau10;
57+
58+
let GENRE_PALETTE = ["#e6a356", "#7fc97f", "#d96c6c", "#a5b1e4", "#ddb892", "#c8a2d6"];
59+
60+
let CB_MODE = localStorage.getItem("mobava_colorblind") === "1";
61+
62+
const _CB_ARCHETYPE = {
63+
immortal: "#009E73",
64+
aaa: "#E69F00",
65+
slow_burn: "#56B4E9",
66+
fading_aaa: "#D55E00",
67+
mid: "#999999",
68+
};
69+
70+
const _DEFAULT_ARCHETYPE = {
71+
immortal: "#7fc97f",
72+
aaa: "#e6a356",
73+
slow_burn: "#8ab4ff",
74+
fading_aaa: "#d96c6c",
75+
mid: "#a69a8c",
76+
};
77+
78+
const _CB_TS_PALETTE = ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7", "#000000"];
79+
const _DEFAULT_TS_PALETTE = d3.schemeTableau10;
80+
81+
const _CB_GENRE_PALETTE = ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#CC79A7"];
82+
const _DEFAULT_GENRE_PALETTE = ["#e6a356", "#7fc97f", "#d96c6c", "#a5b1e4", "#ddb892", "#c8a2d6"];
83+
84+
function _applyCBMode() {
85+
if (CB_MODE) {
86+
ARCHETYPE_COLOR = { ..._CB_ARCHETYPE };
87+
TS_PALETTE = _CB_TS_PALETTE;
88+
GENRE_PALETTE = _CB_GENRE_PALETTE;
89+
} else {
90+
ARCHETYPE_COLOR = { ..._DEFAULT_ARCHETYPE };
91+
TS_PALETTE = _DEFAULT_TS_PALETTE;
92+
GENRE_PALETTE = _DEFAULT_GENRE_PALETTE;
93+
}
94+
}
95+
_applyCBMode();
5796

5897
const FIELD_LABELS = {
5998
completion_rate: "Completion rate",

website/assets/js/landscape.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,17 @@ function onHoverOut() {
247247
tooltip.classed("visible", false);
248248
}
249249

250-
function onBubbleClick(_event, d) {
251-
showCard(d);
250+
function onBubbleClick(event, d) {
251+
if (event.metaKey || event.ctrlKey) {
252+
if (state.selectedIds.has(d.id)) {
253+
state.selectedIds.delete(d.id);
254+
} else {
255+
state.selectedIds.add(d.id);
256+
}
257+
updateSelectionUI();
258+
} else {
259+
showCard(d);
260+
}
252261
}
253262

254263
// GAME CARD

0 commit comments

Comments
 (0)