Skip to content

Commit c1a59e3

Browse files
committed
Render Tools page as eleven sortable grouped accordions - PR_26151_006-tools-page-accordion-count-sort-group
1 parent fe72340 commit c1a59e3

3 files changed

Lines changed: 474 additions & 179 deletions

File tree

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
(function () {
2+
const list = document.querySelector("[data-tools-accordion-list]");
3+
if (!list) {
4+
return;
5+
}
6+
7+
const orderButton = document.querySelector("[data-tools-order]");
8+
const groupedButton = document.querySelector("[data-tools-sort='grouped']");
9+
let currentMode = "ascending";
10+
const toolGroups = [
11+
{
12+
group: "Assets / Content",
13+
tools: [
14+
{
15+
title: "Asset Studio",
16+
href: "asset-studio.html",
17+
image: "../assets/images/tools/asset-studio.png",
18+
description: "Create sprites, animations, vectors and palettes.",
19+
role: "Pixel Smith",
20+
mascot: "pixel-smith",
21+
theme: "pixel"
22+
}
23+
]
24+
},
25+
{
26+
group: "Assets / Content",
27+
tools: [
28+
{
29+
title: "Object Vector Studio",
30+
href: "object-vector-studio.html",
31+
image: "../assets/images/tools/object-vector-studio.png",
32+
description: "Create object-ready vector assets and reusable scene pieces.",
33+
role: "Pixel Smith",
34+
mascot: "pixel-smith",
35+
theme: "pixel"
36+
},
37+
{
38+
title: "World Vector Studio",
39+
href: "world-vector-studio.html",
40+
image: "../assets/images/tools/world-vector-studio.png",
41+
description: "Shape world layouts, maps, terrain, and scene geometry.",
42+
role: "Pixel Smith",
43+
mascot: "pixel-smith",
44+
theme: "pixel"
45+
}
46+
]
47+
},
48+
{
49+
group: "Assets / Content",
50+
tools: [
51+
{
52+
title: "Palette Manager",
53+
href: "palette-manager.html",
54+
image: "../assets/images/tools/palette-manager.png",
55+
description: "Craft and manage color palettes for your games.",
56+
role: "Pixel Smith",
57+
mascot: "pixel-smith",
58+
theme: "pixel"
59+
}
60+
]
61+
},
62+
{
63+
group: "Building / Creation",
64+
tools: [
65+
{
66+
title: "Game Builder",
67+
href: "game-builder.html",
68+
image: "../assets/images/tools/game-builder.png",
69+
description: "Assemble components and build your game from scratch.",
70+
role: "ForgeBot",
71+
mascot: "forgebot",
72+
theme: "forge"
73+
}
74+
]
75+
},
76+
{
77+
group: "Building / Creation",
78+
tools: [
79+
{
80+
title: "Game Design Studio",
81+
href: "game-design-studio.html",
82+
image: "../assets/images/tools/game-design-studio.png",
83+
description: "Design gameplay, systems, rules, and player experience.",
84+
role: "ForgeBot",
85+
mascot: "forgebot",
86+
theme: "forge"
87+
}
88+
]
89+
},
90+
{
91+
group: "Building / Creation",
92+
tools: [
93+
{
94+
title: "Publish Studio",
95+
href: "publisher.html",
96+
image: "../assets/images/tools/publish-studio.png",
97+
description: "Prepare publishing workflows and release-ready game packages.",
98+
role: "ForgeBot",
99+
mascot: "forgebot",
100+
theme: "forge"
101+
}
102+
]
103+
},
104+
{
105+
group: "Design / Animation",
106+
tools: [
107+
{
108+
title: "Animation Studio",
109+
href: "animation-studio.html",
110+
image: "../assets/images/tools/animation-studio.png",
111+
description: "Create timing, animation states, and motion workflows.",
112+
role: "Spark",
113+
mascot: "spark",
114+
theme: "pixel"
115+
},
116+
{
117+
title: "Particle Studio",
118+
href: "particle-studio.html",
119+
image: "../assets/images/tools/particle-studio.png",
120+
description: "Author visual effects, emitters, and particle looks.",
121+
role: "Spark",
122+
mascot: "spark",
123+
theme: "pixel"
124+
}
125+
]
126+
},
127+
{
128+
group: "Media / Audio / Community",
129+
tools: [
130+
{
131+
title: "MIDI Studio",
132+
href: "midi-studio.html",
133+
image: "../assets/images/tools/midi-studio.png",
134+
description: "Build MIDI-driven audio and music interaction flows.",
135+
role: "Foundry Bot",
136+
mascot: "foundry-bot",
137+
theme: "bot"
138+
},
139+
{
140+
title: "Sound Studio",
141+
href: "sound-studio.html",
142+
image: "../assets/images/tools/sound-studio.png",
143+
description: "Prepare audio, sound effects, and game sound workflows.",
144+
role: "Foundry Bot",
145+
mascot: "foundry-bot",
146+
theme: "bot"
147+
}
148+
]
149+
},
150+
{
151+
group: "Technology / System",
152+
tools: [
153+
{
154+
title: "AI Assistant",
155+
href: "ai-assistant.html",
156+
image: "../assets/images/tools/ai-assistant.png",
157+
description: "Get guided technical help for game creation workflows.",
158+
role: "Foundry Bot",
159+
mascot: "foundry-bot",
160+
theme: "bot"
161+
}
162+
]
163+
},
164+
{
165+
group: "Technology / System",
166+
tools: [
167+
{
168+
title: "Code Studio",
169+
href: "code-studio.html",
170+
image: "../assets/images/tools/code-studio.png",
171+
description: "Write code, extend systems and build custom logic.",
172+
role: "Foundry Bot",
173+
mascot: "foundry-bot",
174+
theme: "bot"
175+
},
176+
{
177+
title: "Input Studio",
178+
href: "input-studio.html",
179+
image: "../assets/images/tools/input-studio.png",
180+
description: "Map player controls and configure input workflows.",
181+
role: "Foundry Bot",
182+
mascot: "foundry-bot",
183+
theme: "bot"
184+
}
185+
]
186+
},
187+
{
188+
group: "Settings and Admin",
189+
tools: [
190+
{
191+
title: "Storage Inspector",
192+
href: "storage-inspector.html",
193+
image: "../assets/images/tools/storage-inspector.png",
194+
description: "Inspect local storage, saved state, and tool data.",
195+
role: "Foundry Bot",
196+
mascot: "foundry-bot",
197+
theme: "bot"
198+
}
199+
]
200+
}
201+
];
202+
const allTools = toolGroups.flatMap((group) => group.tools);
203+
204+
function setActiveButton(mode) {
205+
if (orderButton) {
206+
orderButton.setAttribute("aria-pressed", String(mode !== "grouped"));
207+
orderButton.textContent = `Order ${orderButton.dataset.toolsOrder === "ascending" ? "A-Z" : "Z-A"}`;
208+
}
209+
if (groupedButton) {
210+
groupedButton.setAttribute("aria-pressed", String(mode === "grouped"));
211+
}
212+
}
213+
214+
function getOrderedTools(mode) {
215+
const tools = allTools.slice();
216+
tools.sort((left, right) => left.title.localeCompare(right.title));
217+
if (mode === "descending") {
218+
tools.reverse();
219+
}
220+
return tools;
221+
}
222+
223+
function getGroupedTools() {
224+
const groups = [];
225+
toolGroups.forEach((toolGroup) => {
226+
const group = groups.find((entry) => entry.title === toolGroup.group);
227+
if (group) {
228+
group.tools.push(...toolGroup.tools);
229+
return;
230+
}
231+
groups.push({
232+
title: toolGroup.group,
233+
tools: toolGroup.tools.slice()
234+
});
235+
});
236+
return groups;
237+
}
238+
239+
function createToolCard(tool) {
240+
const article = document.createElement("article");
241+
article.className = `tool-card ${tool.theme}`;
242+
article.dataset.mascot = tool.mascot;
243+
244+
const link = document.createElement("a");
245+
link.href = tool.href;
246+
247+
const image = document.createElement("img");
248+
image.src = tool.image;
249+
image.alt = tool.title;
250+
251+
const body = document.createElement("div");
252+
body.className = "body";
253+
254+
const role = document.createElement("span");
255+
role.className = "role";
256+
role.textContent = tool.role;
257+
258+
const title = document.createElement("h3");
259+
title.textContent = tool.title;
260+
261+
const description = document.createElement("p");
262+
description.textContent = tool.description;
263+
264+
const pill = document.createElement("span");
265+
pill.className = "pill";
266+
pill.textContent = "Open page";
267+
268+
body.append(role, title, description, pill);
269+
link.append(image, body);
270+
article.append(link);
271+
return article;
272+
}
273+
274+
function createAccordion(title, tools, isOpen) {
275+
const details = document.createElement("details");
276+
details.className = "vertical-accordion";
277+
details.dataset.toolsAccordion = title;
278+
details.open = isOpen;
279+
280+
const summary = document.createElement("summary");
281+
summary.textContent = title;
282+
283+
const body = document.createElement("div");
284+
body.className = "accordion-body";
285+
286+
const grid = document.createElement("div");
287+
grid.className = "tool-grid";
288+
289+
tools.forEach((tool) => {
290+
grid.append(createToolCard(tool));
291+
});
292+
293+
body.append(grid);
294+
details.append(summary, body);
295+
return details;
296+
}
297+
298+
function createToolGrid(tools) {
299+
const grid = document.createElement("div");
300+
grid.className = "tool-grid";
301+
tools.forEach((tool) => {
302+
grid.append(createToolCard(tool));
303+
});
304+
return grid;
305+
}
306+
307+
function render(mode) {
308+
if (mode === "grouped") {
309+
const accordions = getGroupedTools().map((group, position) => createAccordion(group.title, group.tools, position === 0));
310+
list.replaceChildren(...accordions);
311+
} else {
312+
list.replaceChildren(createToolGrid(getOrderedTools(mode)));
313+
}
314+
currentMode = mode;
315+
setActiveButton(mode);
316+
}
317+
318+
if (orderButton) {
319+
orderButton.addEventListener("click", () => {
320+
const nextOrder = currentMode === "grouped"
321+
? orderButton.dataset.toolsOrder
322+
: orderButton.dataset.toolsOrder === "ascending" ? "descending" : "ascending";
323+
orderButton.dataset.toolsOrder = nextOrder;
324+
render(nextOrder);
325+
});
326+
}
327+
328+
if (groupedButton) {
329+
groupedButton.addEventListener("click", () => {
330+
render("grouped");
331+
});
332+
}
333+
334+
render("ascending");
335+
}());

0 commit comments

Comments
 (0)