| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { SPEEDS, initSpeed, setSpeed, speed } from "./animate.js"; |
| import { confirmOn, initConfirm, setConfirm } from "./confirm.js"; |
| import { boardsMode, initBoards, setBoardsMode } from "./layout.js"; |
| import { initPopups, popupsOn, setPopups } from "./popups.js"; |
| import { node } from "./dom.js"; |
|
|
| const LABELS = { 0: "Off", 0.5: "0.5×", 1: "1×", 2: "2×" }; |
| const TITLES = { |
| 0: "No tile animation: positions change at once", |
| 0.5: "Half speed: every tile easy to follow", |
| 1: "The default pace", |
| 2: "Twice as fast", |
| }; |
|
|
| |
| |
| |
| const GEAR_SVG = |
| '<svg viewBox="0 0 24 24" class="gear-icon" aria-hidden="true"><path d="' + |
| "M12 8.4a3.6 3.6 0 1 0 0 7.2 3.6 3.6 0 0 0 0-7.2zm9 4.9v-2.6l-2.4-.4a6.9 6.9 0 0 0-.8-1.9" + |
| "l1.4-2-1.8-1.8-2 1.4a6.9 6.9 0 0 0-1.9-.8L13.1 3h-2.6l-.4 2.4a6.9 6.9 0 0 0-1.9.8l-2-1.4" + |
| "-1.8 1.8 1.4 2a6.9 6.9 0 0 0-.8 1.9L3 10.7v2.6l2.4.4c.2.7.4 1.3.8 1.9l-1.4 2 1.8 1.8 2-1.4" + |
| "c.6.4 1.2.6 1.9.8l.4 2.4h2.6l.4-2.4c.7-.2 1.3-.4 1.9-.8l2 1.4 1.8-1.8-1.4-2c.4-.6.6-1.2.8-1.9l2.4-.4z" + |
| '"/></svg>'; |
|
|
| function gearIcon() { |
| const holder = document.createElement("span"); |
| holder.innerHTML = GEAR_SVG; |
| return holder.firstChild; |
| } |
|
|
| export function createSettings(host, options = {}) { |
| host.classList.add("settings"); |
| host.innerHTML = ""; |
|
|
| const head = node("div", "settings-head"); |
| const gear = node("button", "gear"); |
| gear.type = "button"; |
| gear.setAttribute("aria-expanded", "false"); |
| gear.appendChild(gearIcon()); |
| gear.appendChild(node("span", null, "Settings")); |
| const summary = node("span", "settings-note", ""); |
| head.append(gear, summary); |
|
|
| const panel = node("div", "settings-panel"); |
| panel.hidden = true; |
| const label = node("span", "settings-label", "Tile animation"); |
| const speeds = node("div", "speeds"); |
| speeds.setAttribute("role", "group"); |
| speeds.setAttribute("aria-label", "Animation speed"); |
| const hint = node("p", "settings-hint", ""); |
| panel.append(label, speeds, hint); |
|
|
| const buttons = SPEEDS.map((value) => { |
| const b = node("button", "speed", LABELS[value]); |
| b.type = "button"; |
| b.dataset.speed = String(value); |
| b.title = TITLES[value]; |
| b.addEventListener("click", () => { |
| setSpeed(value); |
| sync(); |
| if (options.onChange) options.onChange(value); |
| }); |
| speeds.appendChild(b); |
| return b; |
| }); |
|
|
| |
| function flagRow(label, dataKey, titles, get, set) { |
| panel.append(node("i", "settings-gap"), node("span", "settings-label", label)); |
| const flags = node("div", "speeds"); |
| flags.setAttribute("role", "group"); |
| flags.setAttribute("aria-label", label); |
| const pair = [true, false].map((value) => { |
| const b = node("button", "flag", value ? "On" : "Off"); |
| b.type = "button"; |
| b.dataset[dataKey] = String(value); |
| b.title = titles[value ? 0 : 1]; |
| b.addEventListener("click", () => { |
| set(value); |
| sync(); |
| if (options.onChange) options.onChange(speed()); |
| }); |
| flags.appendChild(b); |
| return b; |
| }); |
| panel.append(flags); |
| return { pair, get, dataKey }; |
| } |
|
|
| const flagRows = []; |
| if (options.popups) { |
| initPopups(); |
| flagRows.push( |
| flagRow( |
| "Score pop-ups", |
| "pops", |
| [ |
| "Every point floats off the square that earned it as the round is scored", |
| "Scoring stays in the panel below the boards", |
| ], |
| popupsOn, |
| setPopups |
| ) |
| ); |
| } |
| if (options.confirm) { |
| initConfirm(); |
| flagRows.push( |
| flagRow( |
| "Confirm each move", |
| "confirm", |
| [ |
| "Clicking a row places the tiles; the move plays only when you confirm it", |
| "Clicking a row plays the move at once", |
| ], |
| confirmOn, |
| setConfirm |
| ) |
| ); |
| } |
|
|
| |
| |
| |
| |
| let boardButtons = []; |
| if (options.boards) { |
| initBoards(); |
| panel.append(node("i", "settings-gap"), node("span", "settings-label", "Board size")); |
| const sizes = node("div", "speeds"); |
| sizes.setAttribute("role", "group"); |
| sizes.setAttribute("aria-label", "Board size"); |
| boardButtons = [ |
| ["side", "−", "Everything on one screen: the factories left, both boards beside them"], |
| ["stack", "+", "The classic table: big boards below the factories"], |
| ].map(([mode, label, title]) => { |
| const b = node("button", "flag", label); |
| b.type = "button"; |
| b.dataset.boards = mode; |
| b.title = title; |
| b.addEventListener("click", () => { |
| setBoardsMode(mode); |
| sync(); |
| if (options.onChange) options.onChange(speed()); |
| }); |
| sizes.appendChild(b); |
| return b; |
| }); |
| panel.append(sizes); |
| } |
|
|
| host.append(head, panel); |
|
|
| function sync() { |
| const now = speed(); |
| buttons.forEach((b) => { |
| b.setAttribute("aria-pressed", String(Number(b.dataset.speed) === now)); |
| }); |
| flagRows.forEach(({ pair, get, dataKey }) => { |
| pair.forEach((b) => { |
| b.setAttribute("aria-pressed", String((b.dataset[dataKey] === "true") === get())); |
| }); |
| }); |
| boardButtons.forEach((b) => { |
| b.setAttribute("aria-pressed", String(b.dataset.boards === boardsMode())); |
| }); |
| const bits = [now ? "animation " + LABELS[now] : "animation off"]; |
| if (options.popups) bits.push(popupsOn() ? "pop-ups on" : "pop-ups off"); |
| if (options.confirm) bits.push(confirmOn() ? "confirm on" : "confirm off"); |
| if (options.boards) bits.push(boardsMode() === "stack" ? "boards +" : "boards −"); |
| summary.textContent = bits.join(" · "); |
| |
| |
| hint.textContent = |
| "Tiles always travel to where they land. Slow it down to follow a long turn, " + |
| "or turn it off for instant play."; |
| } |
|
|
| gear.addEventListener("click", () => { |
| const opening = panel.hidden; |
| panel.hidden = !opening; |
| host.classList.toggle("open", opening); |
| gear.setAttribute("aria-expanded", String(opening)); |
| }); |
|
|
| initSpeed(); |
| sync(); |
|
|
| |
| |
| return { el: host, sync, open: () => gear.click(), panel }; |
| } |
|
|