@mideind/netskrafl-react 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/css/netskrafl.css +1526 -1415
- package/dist/cjs/index.js +222 -178
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/css/netskrafl.css +1526 -1415
- package/dist/esm/index.js +222 -178
- package/dist/esm/index.js.map +1 -1
- package/package.json +2 -1
package/dist/cjs/index.js
CHANGED
|
@@ -29206,7 +29206,7 @@ const BestDisplay = () => {
|
|
|
29206
29206
|
// Make sure blank tiles get a different color
|
|
29207
29207
|
for (let i = 0; i < bw.length; i++)
|
|
29208
29208
|
if (bw[i] == '?') {
|
|
29209
|
-
s.push(m("span.blanktile", bw[i + 1]));
|
|
29209
|
+
s.push(m("span.netskrafl-blanktile", bw[i + 1]));
|
|
29210
29210
|
i += 1;
|
|
29211
29211
|
}
|
|
29212
29212
|
else
|
|
@@ -30002,7 +30002,7 @@ const BlankDialog = () => {
|
|
|
30002
30002
|
onclick: (ev) => { game.placeBlank(letter); ev.preventDefault(); },
|
|
30003
30003
|
onmouseover: buttonOver,
|
|
30004
30004
|
onmouseout: buttonOut
|
|
30005
|
-
}, m(".blank-choice.tile.racktile", letter)));
|
|
30005
|
+
}, m(".blank-choice.netskrafl-tile.netskrafl-racktile", letter)));
|
|
30006
30006
|
len--;
|
|
30007
30007
|
}
|
|
30008
30008
|
r.push(m("tr", c));
|
|
@@ -30097,25 +30097,26 @@ const Bag = () => {
|
|
|
30097
30097
|
For further information, see https://github.com/mideind/Netskrafl
|
|
30098
30098
|
|
|
30099
30099
|
*/
|
|
30100
|
-
function makeButton(cls, disabled, onclick, title, children, id) {
|
|
30100
|
+
function makeButton(cls, disabled, onclick, title, children, visible, id) {
|
|
30101
30101
|
// Create a button element, wrapping the disabling logic
|
|
30102
30102
|
// and other boilerplate
|
|
30103
|
-
const
|
|
30103
|
+
const attrs = {
|
|
30104
30104
|
onmouseout: buttonOut,
|
|
30105
30105
|
onmouseover: buttonOver,
|
|
30106
|
+
style: { visibility: visible === false ? "hidden" : "visible" },
|
|
30106
30107
|
};
|
|
30107
30108
|
if (title)
|
|
30108
|
-
|
|
30109
|
+
attrs.title = title;
|
|
30109
30110
|
if (id)
|
|
30110
|
-
|
|
30111
|
+
attrs.id = id;
|
|
30111
30112
|
if (disabled)
|
|
30112
|
-
|
|
30113
|
+
attrs.onclick = (ev) => ev.preventDefault();
|
|
30113
30114
|
else
|
|
30114
|
-
|
|
30115
|
+
attrs.onclick = (ev) => {
|
|
30115
30116
|
onclick && onclick();
|
|
30116
30117
|
ev.preventDefault();
|
|
30117
30118
|
};
|
|
30118
|
-
return m("." + cls + (disabled ? ".disabled" : ""),
|
|
30119
|
+
return m("." + cls + (disabled ? ".disabled" : ""), attrs, children // children may be omitted
|
|
30119
30120
|
);
|
|
30120
30121
|
}
|
|
30121
30122
|
const Score = {
|
|
@@ -30137,20 +30138,20 @@ const Score = {
|
|
|
30137
30138
|
const RecallButton = {
|
|
30138
30139
|
view: (vnode) => {
|
|
30139
30140
|
// Create a tile recall button
|
|
30140
|
-
const { view, game, disabled } = vnode.attrs;
|
|
30141
|
+
const { view, game, disabled, visible = true } = vnode.attrs;
|
|
30141
30142
|
if (!game)
|
|
30142
30143
|
return undefined;
|
|
30143
|
-
return makeButton("recallbtn", !!disabled, () => { game.resetRack(); view.updateScale(); }, ts("Færa stafi aftur í rekka"), glyph("down-arrow"));
|
|
30144
|
+
return makeButton("recallbtn", !!disabled, () => { game.resetRack(); view.updateScale(); }, ts("Færa stafi aftur í rekka"), glyph("down-arrow"), visible);
|
|
30144
30145
|
}
|
|
30145
30146
|
};
|
|
30146
30147
|
const ScrambleButton = {
|
|
30147
30148
|
view: (vnode) => {
|
|
30148
30149
|
// Create a tile scramble button
|
|
30149
|
-
const { game, disabled } = vnode.attrs;
|
|
30150
|
+
const { game, disabled, visible = true } = vnode.attrs;
|
|
30150
30151
|
if (!game)
|
|
30151
30152
|
return undefined;
|
|
30152
30153
|
return makeButton("scramblebtn", !!disabled, () => game.rescrambleRack(), // Note: plain game.rescrambleRack doesn't work here
|
|
30153
|
-
ts("Stokka upp rekka"), glyph("random"));
|
|
30154
|
+
ts("Stokka upp rekka"), glyph("random"), visible);
|
|
30154
30155
|
}
|
|
30155
30156
|
};
|
|
30156
30157
|
const ButtonsRecallScramble = {
|
|
@@ -30166,9 +30167,9 @@ const ButtonsRecallScramble = {
|
|
|
30166
30167
|
if (s.showRecall && !s.showingDialog)
|
|
30167
30168
|
// Show a 'Recall tiles' button
|
|
30168
30169
|
return m(RecallButton, { view, game });
|
|
30169
|
-
|
|
30170
|
-
|
|
30171
|
-
return
|
|
30170
|
+
const visible = s.showScramble && !s.tilesPlaced;
|
|
30171
|
+
const disabled = s.showingDialog;
|
|
30172
|
+
return m(ScrambleButton, { view, game, visible, disabled });
|
|
30172
30173
|
}
|
|
30173
30174
|
};
|
|
30174
30175
|
const Buttons = {
|
|
@@ -30245,7 +30246,7 @@ const Buttons = {
|
|
|
30245
30246
|
}
|
|
30246
30247
|
};
|
|
30247
30248
|
}
|
|
30248
|
-
r.push(makeButton(classes.join("."), s.showingDialog, action, text, legend, "move-mobile"));
|
|
30249
|
+
r.push(makeButton(classes.join("."), s.showingDialog, action, text, legend, true, "move-mobile"));
|
|
30249
30250
|
}
|
|
30250
30251
|
if (s.showForceResignMobile) {
|
|
30251
30252
|
// Force resignation button (only shown on mobile,
|
|
@@ -30264,7 +30265,7 @@ const Buttons = {
|
|
|
30264
30265
|
}
|
|
30265
30266
|
if (s.showExchange) {
|
|
30266
30267
|
// Exchange tiles from the rack
|
|
30267
|
-
const disabled =
|
|
30268
|
+
const disabled = !s.exchangeAllowed || s.tilesPlaced || s.showingDialog;
|
|
30268
30269
|
r.push(makeButton("submitexchange", disabled, () => game.submitExchange(), // Note: plain game.submitExchange doesn't work here
|
|
30269
30270
|
ts("Skipta stöfum"), glyph("refresh")));
|
|
30270
30271
|
}
|
|
@@ -30561,24 +30562,24 @@ const Tile = (initialVnode) => {
|
|
|
30561
30562
|
const isRackTile = coord[0] === 'R';
|
|
30562
30563
|
// A single tile, on the board or in the rack
|
|
30563
30564
|
const t = game.tiles[coord];
|
|
30564
|
-
let classes = [".tile"];
|
|
30565
|
+
let classes = [".netskrafl-tile"];
|
|
30565
30566
|
let attrs = {};
|
|
30566
30567
|
if (t.tile === '?')
|
|
30567
|
-
classes.push("blanktile");
|
|
30568
|
+
classes.push("netskrafl-blanktile");
|
|
30568
30569
|
if (EXTRA_WIDE_LETTERS.includes(t.letter))
|
|
30569
30570
|
// Extra wide letter: handle specially
|
|
30570
|
-
classes.push("extra-wide");
|
|
30571
|
+
classes.push("netskrafl-extra-wide");
|
|
30571
30572
|
else if (WIDE_LETTERS.includes(t.letter))
|
|
30572
30573
|
// Wide letter: handle specially
|
|
30573
|
-
classes.push("wide");
|
|
30574
|
+
classes.push("netskrafl-wide");
|
|
30574
30575
|
if (isRackTile || t.draggable) {
|
|
30575
30576
|
// Rack tile, or at least a draggable one
|
|
30576
|
-
classes.push(opponent ? "freshtile" : "racktile");
|
|
30577
|
+
classes.push(opponent ? "netskrafl-freshtile" : "netskrafl-racktile");
|
|
30577
30578
|
if (isRackTile && game.showingDialog === "exchange") {
|
|
30578
30579
|
// Rack tile, and we're showing the exchange dialog
|
|
30579
30580
|
if (t.xchg)
|
|
30580
30581
|
// Chosen as an exchange tile
|
|
30581
|
-
classes.push("xchgsel");
|
|
30582
|
+
classes.push("netskrafl-xchgsel");
|
|
30582
30583
|
// Exchange dialog is live: add a click handler for the
|
|
30583
30584
|
// exchange state
|
|
30584
30585
|
attrs.onclick = (ev) => {
|
|
@@ -30591,7 +30592,7 @@ const Tile = (initialVnode) => {
|
|
|
30591
30592
|
else if (t.freshtile) {
|
|
30592
30593
|
// A fresh tile on the board that has
|
|
30593
30594
|
// just been played by the opponent
|
|
30594
|
-
classes.push("freshtile");
|
|
30595
|
+
classes.push("netskrafl-freshtile");
|
|
30595
30596
|
}
|
|
30596
30597
|
if (t.index) {
|
|
30597
30598
|
// Make fresh or highlighted tiles appear sequentally by animation
|
|
@@ -32197,11 +32198,11 @@ const vwButtonsReview = (view, moveIndex) => {
|
|
|
32197
32198
|
() => {
|
|
32198
32199
|
// Navigate to previous moveIndex
|
|
32199
32200
|
model.loadBestMoves(moveIndex ? moveIndex - 1 : 0);
|
|
32200
|
-
}, "Sjá fyrri leik", m("span", { id: "nav-prev-visible" }, [glyph("chevron-left"), " Fyrri"]), "navprev"));
|
|
32201
|
+
}, "Sjá fyrri leik", m("span", { id: "nav-prev-visible" }, [glyph("chevron-left"), " Fyrri"]), true, "navprev"));
|
|
32201
32202
|
r.push(makeButton("navbtn", (!moveIndex) || (moveIndex >= numMoves), () => {
|
|
32202
32203
|
// Navigate to next moveIndex
|
|
32203
32204
|
model.loadBestMoves(moveIndex + 1);
|
|
32204
|
-
}, "Sjá næsta leik", m("span", { id: "nav-next-visible" }, ["Næsti ", glyph("chevron-right")]), "navnext"));
|
|
32205
|
+
}, "Sjá næsta leik", m("span", { id: "nav-next-visible" }, ["Næsti ", glyph("chevron-right")]), true, "navnext"));
|
|
32205
32206
|
// Show the score difference between an actual moveIndex and
|
|
32206
32207
|
// a particular moveIndex on the best moveIndex list
|
|
32207
32208
|
if (model.highlightedMove !== null) {
|
|
@@ -32411,39 +32412,68 @@ class View {
|
|
|
32411
32412
|
this.boardScale = 1.0;
|
|
32412
32413
|
const boardParent = document.getElementById("board-parent");
|
|
32413
32414
|
const board = boardParent === null || boardParent === void 0 ? void 0 : boardParent.children[0];
|
|
32414
|
-
if (board)
|
|
32415
|
-
board.
|
|
32415
|
+
if (board) {
|
|
32416
|
+
board.style.transition = 'none';
|
|
32417
|
+
board.style.transformOrigin = 'top left';
|
|
32418
|
+
board.style.transform = `translate(0, 0) scale(1.0)`;
|
|
32419
|
+
}
|
|
32416
32420
|
if (boardParent)
|
|
32417
32421
|
boardParent.scrollTo(0, 0);
|
|
32418
32422
|
}
|
|
32419
32423
|
updateScale() {
|
|
32420
32424
|
var _a;
|
|
32421
32425
|
const model = this.model;
|
|
32422
|
-
|
|
32426
|
+
// Use either the regular game or the riddle (Gáta Dagsins)
|
|
32427
|
+
const game = model.game || model.riddle;
|
|
32423
32428
|
// Update the board scale (zoom)
|
|
32424
32429
|
function scrollIntoView(sq) {
|
|
32425
|
-
// Scroll a square above and to the left of the placed tile into view
|
|
32430
|
+
// Scroll a square above and to the left of the placed tile into view,
|
|
32431
|
+
// with a smooth concurrent zoom and pan animation,
|
|
32432
|
+
// taking clamping into account to ensure that the board always fills
|
|
32433
|
+
// the viewport.
|
|
32434
|
+
const boardParent = document.getElementById("board-parent");
|
|
32435
|
+
const board = boardParent === null || boardParent === void 0 ? void 0 : boardParent.children[0];
|
|
32436
|
+
if (!board || !boardParent)
|
|
32437
|
+
return;
|
|
32426
32438
|
const offset = 3;
|
|
32427
32439
|
const vec = toVector(sq);
|
|
32428
32440
|
const row = Math.max(0, vec.row - offset);
|
|
32429
32441
|
const col = Math.max(0, vec.col - offset);
|
|
32430
32442
|
const c = coord(row, col);
|
|
32431
|
-
|
|
32432
|
-
|
|
32433
|
-
|
|
32434
|
-
// the transform and hence the size of the board has been
|
|
32435
|
-
// updated in the browser, before calculating the client rects
|
|
32436
|
-
if (board)
|
|
32437
|
-
board.setAttribute("style", `transform: scale(${ZOOM_FACTOR})`);
|
|
32443
|
+
// Temporarily set scale to calculate target scroll position
|
|
32444
|
+
board.style.transformOrigin = 'top left';
|
|
32445
|
+
board.style.transform = `translate(0, 0) scale(1.0)`;
|
|
32438
32446
|
const el = document.getElementById("sq_" + c);
|
|
32439
32447
|
const elRect = el === null || el === void 0 ? void 0 : el.getBoundingClientRect();
|
|
32440
|
-
const boardRect = boardParent
|
|
32441
|
-
if (
|
|
32442
|
-
|
|
32443
|
-
|
|
32444
|
-
|
|
32445
|
-
|
|
32446
|
-
|
|
32448
|
+
const boardRect = boardParent.getBoundingClientRect();
|
|
32449
|
+
if (elRect && boardRect) {
|
|
32450
|
+
// Get the dimensions of the scrollable area
|
|
32451
|
+
const viewportWidth = boardParent.clientWidth;
|
|
32452
|
+
const viewportHeight = boardParent.clientHeight;
|
|
32453
|
+
// The offsetWidth/Height include borders, so we use those
|
|
32454
|
+
const scaledBoardWidth = board.offsetWidth * ZOOM_FACTOR;
|
|
32455
|
+
const scaledBoardHeight = board.offsetHeight * ZOOM_FACTOR;
|
|
32456
|
+
// Calculate maximum scroll positions (board edges)
|
|
32457
|
+
const maxScrollLeft = scaledBoardWidth - viewportWidth;
|
|
32458
|
+
const maxScrollTop = scaledBoardHeight - viewportHeight;
|
|
32459
|
+
// Calculate desired scroll position to put the target into
|
|
32460
|
+
// the top left corner of the viewport, or as close as possible
|
|
32461
|
+
let targetScrollLeft = Math.min(elRect.left - boardRect.left, maxScrollLeft);
|
|
32462
|
+
let targetScrollTop = Math.min(elRect.top - boardRect.top, maxScrollTop);
|
|
32463
|
+
// Now animate both translate (for pan) and scale (for zoom) concurrently
|
|
32464
|
+
board.style.transition = 'transform 0.3s ease-in-out';
|
|
32465
|
+
// Note: transforms are applied right to left
|
|
32466
|
+
board.style.transform =
|
|
32467
|
+
`translate(${-targetScrollLeft}px, ${-targetScrollTop}px) scale(${ZOOM_FACTOR})`;
|
|
32468
|
+
// When animation completes, commit to actual scroll position and reset translate
|
|
32469
|
+
board.addEventListener('transitionend', function handler() {
|
|
32470
|
+
// First reset the transform (remove translate, keep scale)
|
|
32471
|
+
board.style.transition = 'none';
|
|
32472
|
+
board.style.transform = `translate(0, 0) scale(${ZOOM_FACTOR})`;
|
|
32473
|
+
// Now set the actual scroll position (already clamped)
|
|
32474
|
+
boardParent.scrollTo(targetScrollLeft, targetScrollTop);
|
|
32475
|
+
board.removeEventListener('transitionend', handler);
|
|
32476
|
+
}, { once: true });
|
|
32447
32477
|
}
|
|
32448
32478
|
}
|
|
32449
32479
|
if (!game || ((_a = model.state) === null || _a === void 0 ? void 0 : _a.uiFullscreen) || game.moveInProgress) {
|
|
@@ -32730,14 +32760,14 @@ class View {
|
|
|
32730
32760
|
// displayed. We therefore allow them to cover the last_chall
|
|
32731
32761
|
// dialog. On mobile, both dialogs are displayed simultaneously.
|
|
32732
32762
|
if (game.last_chall) {
|
|
32733
|
-
r.push(m(".chall-info",
|
|
32763
|
+
r.push(m(".chall-info", [
|
|
32734
32764
|
glyph("info-sign"), nbsp(),
|
|
32735
32765
|
// "Your opponent emptied the rack - you can challenge or pass"
|
|
32736
32766
|
mt("span.pass-explain", "opponent_emptied_rack")
|
|
32737
32767
|
]));
|
|
32738
32768
|
}
|
|
32739
32769
|
if (game.showingDialog == "resign") {
|
|
32740
|
-
r.push(m(".resign",
|
|
32770
|
+
r.push(m(".resign", [
|
|
32741
32771
|
glyph("exclamation-sign"), nbsp(), ts("Viltu gefa leikinn?"), nbsp(),
|
|
32742
32772
|
m("span.mobile-break", m("br")),
|
|
32743
32773
|
m("span.yesnobutton", { onclick: () => game.confirmResign(true) }, [glyph("ok"), ts(" Já")]),
|
|
@@ -32747,7 +32777,7 @@ class View {
|
|
|
32747
32777
|
}
|
|
32748
32778
|
if (game.showingDialog == "pass") {
|
|
32749
32779
|
if (game.last_chall) {
|
|
32750
|
-
r.push(m(".pass-last",
|
|
32780
|
+
r.push(m(".pass-last", [
|
|
32751
32781
|
glyph("forward"), nbsp(), ts("Segja pass?"),
|
|
32752
32782
|
mt("span.pass-explain", "Viðureign lýkur þar með"),
|
|
32753
32783
|
nbsp(),
|
|
@@ -32758,7 +32788,7 @@ class View {
|
|
|
32758
32788
|
]));
|
|
32759
32789
|
}
|
|
32760
32790
|
else {
|
|
32761
|
-
r.push(m(".pass",
|
|
32791
|
+
r.push(m(".pass", [
|
|
32762
32792
|
glyph("forward"), nbsp(), ts("Segja pass?"),
|
|
32763
32793
|
mt("span.pass-explain", "2x3 pöss í röð ljúka viðureign"),
|
|
32764
32794
|
nbsp(), m("span.mobile-break", m("br")),
|
|
@@ -32769,7 +32799,7 @@ class View {
|
|
|
32769
32799
|
}
|
|
32770
32800
|
}
|
|
32771
32801
|
if (game.showingDialog == "exchange") {
|
|
32772
|
-
r.push(m(".exchange",
|
|
32802
|
+
r.push(m(".exchange", [
|
|
32773
32803
|
glyph("refresh"), nbsp(),
|
|
32774
32804
|
ts("Smelltu á flísarnar sem þú vilt skipta"), nbsp(),
|
|
32775
32805
|
m("span.mobile-break", m("br")),
|
|
@@ -32779,7 +32809,7 @@ class View {
|
|
|
32779
32809
|
]));
|
|
32780
32810
|
}
|
|
32781
32811
|
if (game.showingDialog == "chall") {
|
|
32782
|
-
r.push(m(".chall",
|
|
32812
|
+
r.push(m(".chall", [
|
|
32783
32813
|
glyph("ban-circle"), nbsp(), ts("Véfengja lögn?"),
|
|
32784
32814
|
mt("span.pass-explain", "Röng véfenging kostar 10 stig"), nbsp(),
|
|
32785
32815
|
m("span.mobile-break", m("br")),
|
|
@@ -33118,6 +33148,7 @@ class BaseGame {
|
|
|
33118
33148
|
this.currentError = null;
|
|
33119
33149
|
this.currentMessage = null;
|
|
33120
33150
|
this.localturn = true;
|
|
33151
|
+
this.moveInProgress = false;
|
|
33121
33152
|
// UI state
|
|
33122
33153
|
this.showingDialog = null;
|
|
33123
33154
|
this.selectedSq = null;
|
|
@@ -36210,12 +36241,12 @@ class Actions {
|
|
|
36210
36241
|
score: json[userId].score || 0,
|
|
36211
36242
|
timestamp: json[userId].timestamp || ''
|
|
36212
36243
|
}));
|
|
36213
|
-
// Sort by score descending, then by timestamp
|
|
36244
|
+
// Sort by score descending, then by timestamp ascending (earlier first)
|
|
36214
36245
|
entries.sort((a, b) => {
|
|
36215
36246
|
if (b.score !== a.score) {
|
|
36216
36247
|
return b.score - a.score;
|
|
36217
36248
|
}
|
|
36218
|
-
return
|
|
36249
|
+
return a.timestamp.localeCompare(b.timestamp);
|
|
36219
36250
|
});
|
|
36220
36251
|
this.model.leaderboard = entries;
|
|
36221
36252
|
}
|
|
@@ -36581,13 +36612,16 @@ const SunCorona = {
|
|
|
36581
36612
|
const MobileStatus = () => {
|
|
36582
36613
|
return {
|
|
36583
36614
|
view: (vnode) => {
|
|
36584
|
-
const { riddle, selectedMoves, bestMove, onMoveClick } = vnode.attrs;
|
|
36615
|
+
const { riddle, selectedMoves, bestMove, onMoveClick, onStatsClick } = vnode.attrs;
|
|
36585
36616
|
const { bestPossibleScore, globalBestScore } = riddle;
|
|
36586
36617
|
// Determine if player achieved best possible score
|
|
36587
36618
|
const achieved = bestMove !== undefined;
|
|
36588
36619
|
const celebrate = bestMove && bestMove.word !== "";
|
|
36589
36620
|
// Get player's current best score
|
|
36590
|
-
|
|
36621
|
+
// If the player achieved the best possible score, it's in bestMove (not selectedMoves)
|
|
36622
|
+
const playerBestScore = (bestMove && bestMove.word !== "")
|
|
36623
|
+
? bestMove.score
|
|
36624
|
+
: (selectedMoves.length > 0 ? selectedMoves[0].score : 0);
|
|
36591
36625
|
// Determine current leader score (may be this player or another)
|
|
36592
36626
|
let leaderScore = 0;
|
|
36593
36627
|
let isPlayerLeading = false;
|
|
@@ -36602,19 +36636,27 @@ const MobileStatus = () => {
|
|
|
36602
36636
|
}
|
|
36603
36637
|
return m(".mobile-status-container", [
|
|
36604
36638
|
// Current word score (leftmost) - uses RiddleScore component in mobile mode
|
|
36605
|
-
m(".mobile-status-item", m(RiddleScore, { riddle, mode: "mobile" })),
|
|
36606
|
-
//
|
|
36607
|
-
m(".mobile-status-
|
|
36608
|
-
|
|
36609
|
-
|
|
36610
|
-
|
|
36611
|
-
|
|
36612
|
-
|
|
36613
|
-
|
|
36614
|
-
|
|
36639
|
+
m(".mobile-status-item.left", m(RiddleScore, { riddle, mode: "mobile" })),
|
|
36640
|
+
// Interactive card containing player best and leader scores
|
|
36641
|
+
m(".mobile-status-card", {
|
|
36642
|
+
onclick: onStatsClick,
|
|
36643
|
+
title: ts("Tölfræði og stigatafla")
|
|
36644
|
+
}, [
|
|
36645
|
+
// Player's best score
|
|
36646
|
+
m(".mobile-status-card-item.player-best", [
|
|
36647
|
+
m(".mobile-status-label", ts("Þín besta:")),
|
|
36648
|
+
m(".mobile-status-score", playerBestScore.toString())
|
|
36649
|
+
]),
|
|
36650
|
+
// Current leader score
|
|
36651
|
+
m(".mobile-status-card-item.leader" + (isPlayerLeading ? ".is-player" : ""), [
|
|
36652
|
+
m(".mobile-status-label", isPlayerLeading ? ts("Þú leiðir!") : ts("Leiðandi:")),
|
|
36653
|
+
m(".mobile-status-score", leaderScore.toString())
|
|
36654
|
+
]),
|
|
36655
|
+
// Chevron indicator (overlaid at bottom center)
|
|
36656
|
+
m(".mobile-status-card-icon", glyph("chevron-down"))
|
|
36615
36657
|
]),
|
|
36616
36658
|
// Best possible score
|
|
36617
|
-
m(".mobile-status-item.best-possible"
|
|
36659
|
+
m(".mobile-status-item.right.best-possible"
|
|
36618
36660
|
+ (celebrate ? ".celebrate" : "")
|
|
36619
36661
|
+ (achieved ? ".achieved" : ""), {
|
|
36620
36662
|
onclick: () => celebrate && onMoveClick(bestMove.word, bestMove.coord)
|
|
@@ -36651,8 +36693,11 @@ const TabBar = {
|
|
|
36651
36693
|
key: tab.id,
|
|
36652
36694
|
onclick: () => onTabChange(tab.id)
|
|
36653
36695
|
}, [
|
|
36654
|
-
|
|
36655
|
-
tab.
|
|
36696
|
+
m("span.tab-icon-wrapper", [
|
|
36697
|
+
tab.iconGlyph ? m("span.tab-icon", glyph(tab.iconGlyph)) :
|
|
36698
|
+
tab.icon ? m("span.tab-icon", tab.icon) : null,
|
|
36699
|
+
tab.badgeGlyph ? m("span.tab-badge", glyph(tab.badgeGlyph)) : null
|
|
36700
|
+
]),
|
|
36656
36701
|
m("span.tab-label", tab.label)
|
|
36657
36702
|
])));
|
|
36658
36703
|
}
|
|
@@ -37021,18 +37066,26 @@ const LeaderboardView = {
|
|
|
37021
37066
|
const RightSideTabs = () => {
|
|
37022
37067
|
// Component-local state for active tab (defaults to performance)
|
|
37023
37068
|
let activeTab = "performance";
|
|
37024
|
-
const tabs = [
|
|
37025
|
-
{ id: "performance", label: ts("Frammistaða"), iconGlyph: "dashboard" },
|
|
37026
|
-
{ id: "stats", label: ts("Tölfræði"), iconGlyph: "stats" },
|
|
37027
|
-
{ id: "leaderboard", label: ts("Stigatafla"), iconGlyph: "tower" }
|
|
37028
|
-
];
|
|
37029
37069
|
return {
|
|
37030
37070
|
view: (vnode) => {
|
|
37031
37071
|
const { view, selectedMoves, bestMove, onMoveClick } = vnode.attrs;
|
|
37032
|
-
const { riddle, state } = view.model;
|
|
37072
|
+
const { riddle, state, leaderboard } = view.model;
|
|
37033
37073
|
if (!riddle) {
|
|
37034
37074
|
return m(".gatadagsins-right-side-tabs", "");
|
|
37035
37075
|
}
|
|
37076
|
+
// Check if current user is on the leaderboard
|
|
37077
|
+
const currentUserId = (state === null || state === void 0 ? void 0 : state.userId) || "";
|
|
37078
|
+
const isOnLeaderboard = leaderboard && leaderboard.some(entry => entry.userId === currentUserId);
|
|
37079
|
+
const tabs = [
|
|
37080
|
+
{ id: "performance", label: ts("Frammistaða"), iconGlyph: "dashboard" },
|
|
37081
|
+
{ id: "stats", label: ts("Tölfræði"), iconGlyph: "stats" },
|
|
37082
|
+
{
|
|
37083
|
+
id: "leaderboard",
|
|
37084
|
+
label: ts("Stigatafla"),
|
|
37085
|
+
iconGlyph: "tower",
|
|
37086
|
+
badgeGlyph: isOnLeaderboard ? "star" : undefined
|
|
37087
|
+
}
|
|
37088
|
+
];
|
|
37036
37089
|
const handleTabChange = (tabId) => {
|
|
37037
37090
|
activeTab = tabId;
|
|
37038
37091
|
};
|
|
@@ -37087,7 +37140,7 @@ const RightSideTabs = () => {
|
|
|
37087
37140
|
const GataDagsinsRightSide = {
|
|
37088
37141
|
// Component containing both mobile status bar and desktop tabbed view
|
|
37089
37142
|
view: (vnode) => {
|
|
37090
|
-
const { view, selectedMoves, bestMove } = vnode.attrs;
|
|
37143
|
+
const { view, selectedMoves, bestMove, onStatsClick } = vnode.attrs;
|
|
37091
37144
|
const { riddle } = view.model;
|
|
37092
37145
|
const handleMoveClick = (word, coord) => {
|
|
37093
37146
|
if (riddle && word && coord) {
|
|
@@ -37101,7 +37154,8 @@ const GataDagsinsRightSide = {
|
|
|
37101
37154
|
riddle,
|
|
37102
37155
|
selectedMoves,
|
|
37103
37156
|
bestMove,
|
|
37104
|
-
onMoveClick: handleMoveClick
|
|
37157
|
+
onMoveClick: handleMoveClick,
|
|
37158
|
+
onStatsClick
|
|
37105
37159
|
})),
|
|
37106
37160
|
// Desktop-only tabbed view (hidden on mobile, visible on desktop)
|
|
37107
37161
|
m(".gatadagsins-thermometer-column", m(RightSideTabs, {
|
|
@@ -37131,64 +37185,72 @@ const GataDagsinsRightSide = {
|
|
|
37131
37185
|
const GataDagsinsHelp = {
|
|
37132
37186
|
view: (vnode) => {
|
|
37133
37187
|
const closeHelp = vnode.attrs.onClose;
|
|
37134
|
-
return
|
|
37135
|
-
//
|
|
37136
|
-
m(".modal-
|
|
37137
|
-
|
|
37138
|
-
|
|
37139
|
-
|
|
37140
|
-
|
|
37141
|
-
|
|
37142
|
-
|
|
37143
|
-
|
|
37144
|
-
|
|
37145
|
-
|
|
37146
|
-
|
|
37147
|
-
|
|
37148
|
-
|
|
37149
|
-
m("li", "Þú færð borð með allmörgum stöfum sem þegar hafa verið lagðir."),
|
|
37150
|
-
m("li", "Neðst á skjánum eru stafaflísar sem þú getur notað til að mynda orð."),
|
|
37151
|
-
m("li", "Dragðu flísar á borðið til að mynda orð, annaðhvort lárétt eða lóðrétt."),
|
|
37152
|
-
m("li", "Orðin verða að tengjast við stafi sem fyrir eru á borðinu."),
|
|
37153
|
-
m("li", "Þú sérð jafnóðum hvort lögnin á borðinu er gild og hversu mörg stig hún gefur."),
|
|
37154
|
-
m("li", "Þú getur prófað eins mörg orð og þú vilt - besta skorið þitt er vistað."),
|
|
37155
|
-
]),
|
|
37156
|
-
m("h3", "Stigagjöf"),
|
|
37157
|
-
m("p", "Þú færð stig fyrir hvern staf í orðinu, auk bónusstiga fyrir lengri orð:"),
|
|
37158
|
-
m("ul", [
|
|
37159
|
-
m("li", "Hver stafur gefur 1-10 stig eftir gildi hans"),
|
|
37160
|
-
m("li", "Orð sem nota allar 7 stafaflísarnar gefa 50 stiga bónus"),
|
|
37161
|
-
m("li", "Sumir reitir á borðinu tvöfalda eða þrefalda stafagildið"),
|
|
37162
|
-
m("li", "Sumir reitir tvöfalda eða þrefalda heildarorðagildið"),
|
|
37163
|
-
]),
|
|
37164
|
-
m("h3", "Hitamælir"),
|
|
37165
|
-
m("p", "Hitamælirinn hægra megin (eða efst á farsímum) sýnir:"),
|
|
37166
|
-
m("ul", [
|
|
37167
|
-
m("li", m("strong", "Besta mögulega skor:"), " Hæstu stig sem hægt er að ná á þessu borði."),
|
|
37168
|
-
m("li", m("strong", "Besta skor dagsins:"), " Hæstu stig sem einhver leikmaður hefur náð í dag."),
|
|
37169
|
-
m("li", m("strong", "Þín bestu orð:"), " Orðin sem þú hefur lagt og stigin fyrir þau."),
|
|
37170
|
-
m("li", "Þú getur smellt á orð á hitamælinum til að fá þá lögn aftur á borðið."),
|
|
37171
|
-
]),
|
|
37172
|
-
m("h3", "Ábendingar"),
|
|
37173
|
-
m("ul", [
|
|
37174
|
-
m("li", "Reyndu að nota dýra stafi (eins og X, Ý, Þ) á tvöföldunar- eða þreföldunarreitum."),
|
|
37175
|
-
m("li", "Lengri orð gefa mun fleiri stig vegna bónussins."),
|
|
37176
|
-
m("li", "Þú getur dregið allar flísar til baka með bláa endurkalls-hnappnum."),
|
|
37177
|
-
m("li", "Ný gáta birtist á hverjum nýjum degi - klukkan 00:00!"),
|
|
37188
|
+
return [
|
|
37189
|
+
// Backdrop
|
|
37190
|
+
m(".modal-backdrop-netskrafl", {
|
|
37191
|
+
onclick: (e) => { e.preventDefault(); },
|
|
37192
|
+
onwheel: (e) => { e.preventDefault(); e.stopPropagation(); },
|
|
37193
|
+
ontouchmove: (e) => { e.preventDefault(); e.stopPropagation(); }
|
|
37194
|
+
}),
|
|
37195
|
+
m(".modal-dialog.gatadagsins-help", m(".modal-content", [
|
|
37196
|
+
// Header with close button
|
|
37197
|
+
m(".modal-header", [
|
|
37198
|
+
m("h2", "Um Gátu dagsins"),
|
|
37199
|
+
m("button.close", {
|
|
37200
|
+
onclick: closeHelp,
|
|
37201
|
+
"aria-label": "Loka"
|
|
37202
|
+
}, m("span", { "aria-hidden": "true" }, "×"))
|
|
37178
37203
|
]),
|
|
37179
|
-
|
|
37180
|
-
m("
|
|
37181
|
-
"Gáta dagsins er
|
|
37182
|
-
|
|
37183
|
-
",
|
|
37184
|
-
"
|
|
37204
|
+
// Body with help content
|
|
37205
|
+
m(".modal-body", [
|
|
37206
|
+
m("p", "Gáta dagsins er dagleg krossgátuþraut, svipuð skrafli, þar sem þú reynir að finna " +
|
|
37207
|
+
"stigahæsta orðið sem hægt er að mynda með gefnum stöfum."),
|
|
37208
|
+
m("h3", "Hvernig á að spila"),
|
|
37209
|
+
m("ul", [
|
|
37210
|
+
m("li", "Þú færð borð með allmörgum stöfum sem þegar hafa verið lagðir."),
|
|
37211
|
+
m("li", "Neðst á skjánum eru stafaflísar sem þú getur notað til að mynda orð."),
|
|
37212
|
+
m("li", "Dragðu flísar á borðið til að mynda orð, annaðhvort lárétt eða lóðrétt."),
|
|
37213
|
+
m("li", "Orðin verða að tengjast við stafi sem fyrir eru á borðinu."),
|
|
37214
|
+
m("li", "Þú sérð jafnóðum hvort lögnin á borðinu er gild og hversu mörg stig hún gefur."),
|
|
37215
|
+
m("li", "Þú getur prófað eins mörg orð og þú vilt - besta skorið þitt er vistað."),
|
|
37216
|
+
]),
|
|
37217
|
+
m("h3", "Stigagjöf"),
|
|
37218
|
+
m("p", "Þú færð stig fyrir hvern staf í orðinu, auk bónusstiga fyrir lengri orð:"),
|
|
37219
|
+
m("ul", [
|
|
37220
|
+
m("li", "Hver stafur gefur 1-10 stig eftir gildi hans"),
|
|
37221
|
+
m("li", "Orð sem nota allar 7 stafaflísarnar gefa 50 stiga bónus"),
|
|
37222
|
+
m("li", "Sumir reitir á borðinu tvöfalda eða þrefalda stafagildið"),
|
|
37223
|
+
m("li", "Sumir reitir tvöfalda eða þrefalda heildarorðagildið"),
|
|
37224
|
+
]),
|
|
37225
|
+
m("h3", "Hitamælir"),
|
|
37226
|
+
m("p", "Hitamælirinn hægra megin (eða efst á farsímum) sýnir:"),
|
|
37227
|
+
m("ul", [
|
|
37228
|
+
m("li", m("strong", "Besta mögulega skor:"), " Hæstu stig sem hægt er að ná á þessu borði."),
|
|
37229
|
+
m("li", m("strong", "Besta skor dagsins:"), " Hæstu stig sem einhver leikmaður hefur náð í dag."),
|
|
37230
|
+
m("li", m("strong", "Þín bestu orð:"), " Orðin sem þú hefur lagt og stigin fyrir þau."),
|
|
37231
|
+
m("li", "Þú getur smellt á orð á hitamælinum til að fá þá lögn aftur á borðið."),
|
|
37232
|
+
]),
|
|
37233
|
+
m("h3", "Ábendingar"),
|
|
37234
|
+
m("ul", [
|
|
37235
|
+
m("li", "Reyndu að nota dýra stafi (eins og X, Ý, Þ) á tvöföldunar- eða þreföldunarreitum."),
|
|
37236
|
+
m("li", "Lengri orð gefa mun fleiri stig vegna bónussins."),
|
|
37237
|
+
m("li", "Þú getur dregið allar flísar til baka með bláa endurkalls-hnappnum."),
|
|
37238
|
+
m("li", "Ný gáta birtist á hverjum nýjum degi - klukkan 00:00!"),
|
|
37239
|
+
]),
|
|
37240
|
+
m("h3", "Um leikinn"),
|
|
37241
|
+
m("p", [
|
|
37242
|
+
"Gáta dagsins er systkini ",
|
|
37243
|
+
m("a", { href: "https://netskrafl.is", target: "_blank" }, "Netskrafls"),
|
|
37244
|
+
", hins sívinsæla íslenska krossgátuleiks á netinu. ",
|
|
37245
|
+
"Leikurinn er þróaður af Miðeind ehf."
|
|
37246
|
+
]),
|
|
37185
37247
|
]),
|
|
37186
|
-
|
|
37187
|
-
|
|
37188
|
-
|
|
37189
|
-
|
|
37190
|
-
|
|
37191
|
-
]
|
|
37248
|
+
// Footer with close button
|
|
37249
|
+
m(".modal-footer", m("button.btn.btn-primary", {
|
|
37250
|
+
onclick: closeHelp
|
|
37251
|
+
}, "Loka"))
|
|
37252
|
+
])),
|
|
37253
|
+
];
|
|
37192
37254
|
}
|
|
37193
37255
|
};
|
|
37194
37256
|
|
|
@@ -37209,35 +37271,46 @@ const GataDagsinsHelp = {
|
|
|
37209
37271
|
const StatsModal = () => {
|
|
37210
37272
|
// Component-local state for active tab (defaults to stats)
|
|
37211
37273
|
let activeTab = "stats";
|
|
37212
|
-
const tabs = [
|
|
37213
|
-
{ id: "stats", label: ts("Tölfræði"), iconGlyph: "stats" },
|
|
37214
|
-
{ id: "leaderboard", label: ts("Stigatafla"), iconGlyph: "tower" }
|
|
37215
|
-
];
|
|
37216
37274
|
return {
|
|
37217
37275
|
view: (vnode) => {
|
|
37218
37276
|
const { view, onClose } = vnode.attrs;
|
|
37219
|
-
const { riddle, state } = view.model;
|
|
37277
|
+
const { riddle, state, leaderboard } = view.model;
|
|
37220
37278
|
if (!riddle) {
|
|
37221
37279
|
return null;
|
|
37222
37280
|
}
|
|
37281
|
+
// Check if current user is on the leaderboard
|
|
37282
|
+
const currentUserId = (state === null || state === void 0 ? void 0 : state.userId) || "";
|
|
37283
|
+
const isOnLeaderboard = leaderboard && leaderboard.some(entry => entry.userId === currentUserId);
|
|
37284
|
+
const tabs = [
|
|
37285
|
+
{ id: "stats", label: ts("Tölfræði"), iconGlyph: "stats" },
|
|
37286
|
+
{
|
|
37287
|
+
id: "leaderboard",
|
|
37288
|
+
label: ts("Stigatafla"),
|
|
37289
|
+
iconGlyph: "tower",
|
|
37290
|
+
badgeGlyph: isOnLeaderboard ? "star" : undefined
|
|
37291
|
+
}
|
|
37292
|
+
];
|
|
37223
37293
|
const handleTabChange = (tabId) => {
|
|
37224
37294
|
activeTab = tabId;
|
|
37225
37295
|
};
|
|
37226
37296
|
return [
|
|
37227
37297
|
// Backdrop
|
|
37228
|
-
m(".modal-backdrop", {
|
|
37229
|
-
onclick:
|
|
37298
|
+
m(".modal-backdrop-netskrafl", {
|
|
37299
|
+
onclick: (e) => { e.preventDefault(); },
|
|
37300
|
+
onwheel: (e) => { e.preventDefault(); e.stopPropagation(); },
|
|
37301
|
+
ontouchmove: (e) => { e.preventDefault(); e.stopPropagation(); }
|
|
37230
37302
|
}),
|
|
37231
37303
|
// Modal dialog
|
|
37232
|
-
m(".modal-dialog.stats-modal",
|
|
37304
|
+
m(".modal-dialog.stats-modal", {
|
|
37305
|
+
onwheel: (e) => { e.stopPropagation(); },
|
|
37306
|
+
ontouchmove: (e) => { e.stopPropagation(); }
|
|
37307
|
+
}, [
|
|
37233
37308
|
m(".modal-content", [
|
|
37234
|
-
//
|
|
37235
|
-
m(".
|
|
37236
|
-
|
|
37237
|
-
|
|
37238
|
-
|
|
37239
|
-
}, "×")
|
|
37240
|
-
]),
|
|
37309
|
+
// Close button in top right
|
|
37310
|
+
m("button.close", {
|
|
37311
|
+
onclick: onClose,
|
|
37312
|
+
"aria-label": "Loka"
|
|
37313
|
+
}, glyph("remove")),
|
|
37241
37314
|
// Tab navigation
|
|
37242
37315
|
m(TabBar, {
|
|
37243
37316
|
tabs,
|
|
@@ -37264,30 +37337,6 @@ const StatsModal = () => {
|
|
|
37264
37337
|
};
|
|
37265
37338
|
};
|
|
37266
37339
|
|
|
37267
|
-
/*
|
|
37268
|
-
|
|
37269
|
-
MobileStatsButton.ts
|
|
37270
|
-
|
|
37271
|
-
Button to open stats modal on mobile
|
|
37272
|
-
|
|
37273
|
-
Copyright (C) 2025 Miðeind ehf.
|
|
37274
|
-
Author: Vilhjálmur Þorsteinsson
|
|
37275
|
-
|
|
37276
|
-
The Creative Commons Attribution-NonCommercial 4.0
|
|
37277
|
-
International Public License (CC-BY-NC 4.0) applies to this software.
|
|
37278
|
-
For further information, see https://github.com/mideind/Netskrafl
|
|
37279
|
-
|
|
37280
|
-
*/
|
|
37281
|
-
const MobileStatsButton = {
|
|
37282
|
-
view: (vnode) => {
|
|
37283
|
-
const { onClick } = vnode.attrs;
|
|
37284
|
-
return m(".mobile-stats-button", {
|
|
37285
|
-
onclick: onClick,
|
|
37286
|
-
title: "Tölfræði og stigatafla"
|
|
37287
|
-
}, m(".stats-icon", glyph("stats")));
|
|
37288
|
-
}
|
|
37289
|
-
};
|
|
37290
|
-
|
|
37291
37340
|
/*
|
|
37292
37341
|
|
|
37293
37342
|
GataDagsins.ts
|
|
@@ -37393,7 +37442,7 @@ const GataDagsins$1 = () => {
|
|
|
37393
37442
|
// Board and rack component (left side)
|
|
37394
37443
|
m(GataDagsinsBoardAndRack, { view }),
|
|
37395
37444
|
// Right-side component with scores and comparisons
|
|
37396
|
-
m(GataDagsinsRightSide, { view, selectedMoves, bestMove }),
|
|
37445
|
+
m(GataDagsinsRightSide, { view, selectedMoves, bestMove, onStatsClick: toggleStatsModal }),
|
|
37397
37446
|
// Blank dialog
|
|
37398
37447
|
riddle.askingForBlank
|
|
37399
37448
|
? m(BlankDialog, { game: riddle })
|
|
@@ -37407,13 +37456,8 @@ const GataDagsins$1 = () => {
|
|
|
37407
37456
|
((_a = model.state) === null || _a === void 0 ? void 0 : _a.beginner) ? m(Beginner, { view }) : "",
|
|
37408
37457
|
// Custom Info button for GataDagsins that shows help dialog
|
|
37409
37458
|
m(".info", { title: ts("Upplýsingar og hjálp") }, m("a.iconlink", { href: "#", onclick: (e) => { e.preventDefault(); toggleHelp(); } }, glyph("info-sign"))),
|
|
37410
|
-
// Mobile stats button (hidden on desktop)
|
|
37411
|
-
m(MobileStatsButton, { onClick: toggleStatsModal }),
|
|
37412
37459
|
// Help dialog and backdrop
|
|
37413
|
-
showHelp ?
|
|
37414
|
-
m(".modal-backdrop", { onclick: (e) => { e.preventDefault(); } }),
|
|
37415
|
-
m(GataDagsinsHelp, { onClose: toggleHelp })
|
|
37416
|
-
] : "",
|
|
37460
|
+
showHelp ? m(GataDagsinsHelp, { onClose: toggleHelp }) : "",
|
|
37417
37461
|
// Stats modal and backdrop (mobile only)
|
|
37418
37462
|
showStatsModal ? m(StatsModal, { view, onClose: toggleStatsModal }) : "",
|
|
37419
37463
|
]);
|