diff --git a/.gitignore b/.gitignore
index efbc4095..f4a010bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,7 +51,6 @@ testem.log
._*
.AppleDouble
.LSOverride
-Icon?
Thumbs.db
# neutralino
diff --git a/README.md b/README.md
index 9745bb52..f0ea9c5d 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ The original open-source Mahjong Solitaire game powering many Mahjong experience
🖼️ **Massive visual customization** - 8 image backgrounds, 375 pattern backgrounds, light/dark mode, 14 color themes
-🏆 **3 difficulty levels** - from relaxed casual play to expert-level challenge
+🏆 **Difficulty levels** - from relaxed casual play to expert-level challenge
💾 **Auto-save** - your game state and best times are saved locally in your browser, never to the cloud
@@ -111,7 +111,7 @@ Most modern phones use `arm64`. Try these APK variants in order:
## 🙏 Acknowledgements
-Mah's art is built on open-source creative work. See the credits for [artwork](src/assets/svg/README.md), [backgrounds](src/assets/img/README.md), [sounds](src/assets/sounds/README.md), and [fonts](src/fonts/README.md).
+Mah's art is built on open-source creative work. See the credits for [artwork](src/assets/svg/README.md), [backgrounds](src/assets/img/README.md), [sounds](src/assets/sounds/README.md), [icons](src/app/components/icons/README.md) and [fonts](src/fonts/README.md).
---
diff --git a/angular.json b/angular.json
index 38899df2..ea562210 100644
--- a/angular.json
+++ b/angular.json
@@ -33,8 +33,6 @@
"src/assets"
],
"styles": [
- "src/fonts/fontello/css/mah.css",
- "src/fonts/editor/css/editor.css",
"src/fonts/kulim-park/css/kulim-park.css",
"src/styles.scss"
],
diff --git a/resources/images/svg-to-png.mjs b/resources/images/svg-to-png.mjs
index 790bb204..a769efc5 100644
--- a/resources/images/svg-to-png.mjs
+++ b/resources/images/svg-to-png.mjs
@@ -573,7 +573,7 @@ async function main() {
const sample = nameGrid.flat().filter(Boolean).slice(0, 5).join(", ");
console.log(`[t_preview] Detected layout ${cols}x${rows}; sample names: ${sample}`);
} else {
- console.log("[t_preview] Not found or unparsable — used fallback grid.");
+ console.log("[t_preview] Not found or unparsable - used fallback grid.");
}
}
}
diff --git a/src/app/components/choose-layout/choose-layout.component.html b/src/app/components/choose-layout/choose-layout.component.html
index 0578ff65..13ca9bd3 100644
--- a/src/app/components/choose-layout/choose-layout.component.html
+++ b/src/app/components/choose-layout/choose-layout.component.html
@@ -1,24 +1,22 @@
+@if (activeInfo()) {
+
+
+
+}
diff --git a/src/app/components/choose-layout/choose-layout.component.scss b/src/app/components/choose-layout/choose-layout.component.scss
index 7926b4d8..e1293471 100644
--- a/src/app/components/choose-layout/choose-layout.component.scss
+++ b/src/app/components/choose-layout/choose-layout.component.scss
@@ -8,6 +8,7 @@
display: flex;
flex-direction: column;
overflow: hidden;
+ position: relative;
.choose-buttons {
padding-top: 10px;
@@ -25,8 +26,11 @@
flex: 1;
label {
- display: block;
- margin-bottom: 2px;
+ padding: 1px 5px 5px;
+ display: flex;
+ align-items: center;
+ user-select: none;
+ gap: 4px;
}
select {
@@ -34,16 +38,6 @@
}
}
- .mode {
- max-width: 30%;
- }
-
- @include mixins.respond-to-height(medium-down) {
- .mode {
- max-width: 50%;
- }
- }
-
@include mixins.respond-to-height(small-down) {
padding-top: 4px;
}
@@ -65,7 +59,7 @@
align-items: unset;
label {
- font-size: 0.7em;
+ font-size: 0.9em;
}
.mode {
@@ -77,5 +71,82 @@
}
}
}
+
+ label.info-label {
+ cursor: pointer;
+ transition: color 0.15s;
+
+ &:hover {
+ color: var(--text-highlight-color);
+ }
+ }
+
+ .info-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgb(0 0 0 / 40%);
+ z-index: 100;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .info-popup {
+ position: relative;
+ background: var(--dialog-background-color);
+ border: 1px solid var(--dialog-border-color);
+ border-radius: 12px;
+ padding: 1.2em 1.5em 1em;
+ max-width: 90%;
+ max-height: 90%;
+ overflow-y: auto;
+ box-shadow: var(--overlay-popup-shadow);
+ color: var(--main-content-text-color);
+
+ h2 {
+ margin: 0 0 0.8em;
+ font-size: 1em;
+ color: var(--dialog-headline-color);
+ text-align: center;
+ }
+
+ .close {
+ position: absolute;
+ top: 0.5em;
+ right: 0.6em;
+ cursor: pointer;
+ color: var(--close-color);
+ line-height: 1;
+
+ &:hover {
+ color: var(--close-color-hover);
+ }
+ }
+
+ .info-item {
+ padding: 0.5em 0;
+ border-bottom: 1px solid var(--dialog-border-color);
+
+ &:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+ }
+
+ strong {
+ display: block;
+ margin-bottom: 0.2em;
+ }
+
+ span, ul {
+ color: var(--main-content-text-color-muted);
+ font-size: 0.85em;
+ }
+
+ ul {
+ margin: 0;
+ padding-left: 1.2em;
+ }
+ }
+ }
+ }
}
diff --git a/src/app/components/choose-layout/choose-layout.component.spec.ts b/src/app/components/choose-layout/choose-layout.component.spec.ts
index 30fdbbf7..896373a1 100644
--- a/src/app/components/choose-layout/choose-layout.component.spec.ts
+++ b/src/app/components/choose-layout/choose-layout.component.spec.ts
@@ -57,8 +57,8 @@ describe('ChooseLayoutComponent', () => {
it('should initialize with default values', () => {
expect(component.gameMode()).toBe(GAME_MODE_EASY);
- expect(component.buildMode).toBe(MODE_SOLVABLE);
- expect(component.buildModes).toHaveLength(2);
+ expect(component.buildMode()).toBe(MODE_SOLVABLE);
+ expect(component.buildModes).toHaveLength(4);
expect(component.gameModes).toHaveLength(3);
});
@@ -111,7 +111,7 @@ describe('ChooseLayoutComponent', () => {
fixture.detectChanges();
// Assert
- expect(component.buildMode).toBe(MODE_RANDOM);
+ expect(component.buildMode()).toBe(MODE_RANDOM);
});
it('should update gameMode when select is changed', () => {
diff --git a/src/app/components/choose-layout/choose-layout.component.ts b/src/app/components/choose-layout/choose-layout.component.ts
index 0e15be72..441c945f 100644
--- a/src/app/components/choose-layout/choose-layout.component.ts
+++ b/src/app/components/choose-layout/choose-layout.component.ts
@@ -1,11 +1,13 @@
-import { Component, inject, model, output } from '@angular/core';
-import { type BUILD_MODE_ID, BuilderModes, MODE_SOLVABLE } from '../../model/builder';
+import { Component, inject, model, output, signal } from '@angular/core';
+import { type BUILD_MODE_ID, BuilderModes, MODE_SOLVABLE, solvableModeForGameMode } from '../../model/builder';
import type { Layout } from '../../model/types';
import { LayoutService } from '../../service/layout.service';
import { LocalstorageService } from '../../service/localstorage.service';
import { type GAME_MODE_ID, GameModes } from '../../model/consts';
import { TranslatePipe } from '@ngx-translate/core';
import { LayoutListComponent } from '../layout-list/layout-list.component';
+import { IconInfoComponent } from '../icons/icon-info.component';
+import { IconCloseComponent } from '../icons/icon-close.component';
export interface StartEvent {
layout: Layout;
@@ -17,20 +19,26 @@ export interface StartEvent {
selector: 'app-choose-layout',
templateUrl: './choose-layout.component.html',
styleUrls: ['./choose-layout.component.scss'],
- imports: [LayoutListComponent, TranslatePipe]
+ imports: [LayoutListComponent, TranslatePipe, IconInfoComponent, IconCloseComponent]
})
export class ChooseLayoutComponent {
readonly startEvent = output();
readonly gameMode = model.required();
- buildMode: BUILD_MODE_ID = MODE_SOLVABLE;
+ readonly buildMode = model(MODE_SOLVABLE);
buildModes = BuilderModes;
gameModes = GameModes;
+ activeInfo = signal<'generator' | 'mode' | null>(null);
layoutService = inject(LayoutService);
storage = inject(LocalstorageService);
+ onGameModeChange(mode: GAME_MODE_ID): void {
+ this.gameMode.set(mode);
+ this.buildMode.set(solvableModeForGameMode(mode));
+ }
+
onStart(layout: Layout): void {
if (layout) {
- this.startEvent.emit({ layout, buildMode: this.buildMode, gameMode: this.gameMode() });
+ this.startEvent.emit({ layout, buildMode: this.buildMode(), gameMode: this.gameMode() });
this.storage.storeLastPlayed(layout.id);
}
}
diff --git a/src/app/components/dialog/dialog.component.html b/src/app/components/dialog/dialog.component.html
index b8c49a5c..bf45fdf0 100644
--- a/src/app/components/dialog/dialog.component.html
+++ b/src/app/components/dialog/dialog.component.html
@@ -3,10 +3,10 @@