diff --git a/tests/static-smoke.test.mjs b/tests/static-smoke.test.mjs index a617677..75c6324 100644 --- a/tests/static-smoke.test.mjs +++ b/tests/static-smoke.test.mjs @@ -2,6 +2,85 @@ import { readFile } from "node:fs/promises"; import { test } from "node:test"; import assert from "node:assert/strict"; +function valueAt(object, path) { + return path.split(".").reduce((value, key) => value?.[key], object); +} + +function assertNonEmptyString(object, path) { + const value = valueAt(object, path); + assert.equal(typeof value, "string", `${path} must be a string`); + assert.ok(value.trim(), `${path} must not be empty`); +} + +function assertStringArray(object, path, minLength = 1) { + const value = valueAt(object, path); + assert.ok(Array.isArray(value), `${path} must be an array`); + assert.ok(value.length >= minLength, `${path} must contain at least ${minLength} item(s)`); + value.forEach((item, index) => { + assert.equal(typeof item, "string", `${path}[${index}] must be a string`); + assert.ok(item.trim(), `${path}[${index}] must not be empty`); + }); +} + +function validateSiteContent(site) { + [ + "brand.name", + "brand.homeAriaLabel", + "navigation.home", + "navigation.toolboxes", + "navigation.games", + "navigation.mobileGames", + "sidebar.badge", + "sidebar.note", + "topbar.dashboard", + "topbar.toolbox", + "topbar.games", + "gamesPage.eyebrow", + "gamesPage.title", + "gamesPage.description", + "gamesPage.emptyTitle", + "gamesPage.emptyText", + "home.hero.eyebrow", + "home.hero.title", + "home.hero.description", + "home.hero.primaryAction", + "home.hero.secondaryAction", + "home.stats.toolboxSingular", + "home.stats.toolboxPlural", + "home.stats.toolSingular", + "home.stats.toolPlural", + "home.origin.eyebrow", + "home.origin.title", + "home.origin.visualAlt", + "home.origin.dialogueAriaLabel", + "home.origin.caption", + "home.legal.copyright", + "home.legal.disclaimer", + "toolboxes.eyebrow", + "toolboxes.title", + "toolboxes.newButton", + "toolboxes.importAll", + "toolboxes.exportAll", + "toolboxes.importOne", + "toolboxes.summary", + "toolboxes.storageHelp.title", + "toolboxes.storageHelp.text", + "toolboxes.emptyTitle", + "toolboxes.emptyText" + ].forEach((path) => assertNonEmptyString(site, path)); + + assertStringArray(site, "toolboxes.storageHelp.items", 3); + + const lines = valueAt(site, "home.origin.lines"); + assert.ok(Array.isArray(lines), "home.origin.lines must be an array"); + assert.ok(lines.length >= 2, "home.origin.lines must contain at least 2 items"); + lines.forEach((line, index) => { + assert.ok(["user", "app"].includes(line?.speaker), `home.origin.lines[${index}].speaker must be "user" or "app"`); + assert.equal(typeof line?.text, "string", `home.origin.lines[${index}].text must be a string`); + assert.ok(line.text.trim(), `home.origin.lines[${index}].text must not be empty`); + }); +} + test("vite entrypoint loads the react application", async () => { const html = await readFile("website/index.html", "utf8"); @@ -150,6 +229,11 @@ test("react application defines the expected local toolbox primitives", async () assert.match(screenshotsModule, /dataTransfer\.files/); }); +test("site content json is complete", async () => { + const site = JSON.parse(await readFile("website/public/data/site.json", "utf8")); + validateSiteContent(site); +}); + test("mhwilds data and assets are available", async () => { const site = JSON.parse(await readFile("website/public/data/site.json", "utf8")); const games = JSON.parse(await readFile("website/public/data/games.json", "utf8")); diff --git a/website/public/data/site.json b/website/public/data/site.json index 708b2e3..5bf00f6 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -75,6 +75,10 @@ "text": "Merci ! :)" } ] + }, + "legal": { + "copyright": "© 2026 Shinuwa / Sokko G. Tous droits réservés.", + "disclaimer": "Sokko G est un projet non officiel. Les noms, logos, images, illustrations et contenus liés aux jeux présentés appartiennent à leurs éditeurs et ayants droit respectifs. Les visuels officiels utilisés sur le site restent la propriété de leurs détenteurs." } }, "toolboxes": { diff --git a/website/src/main.jsx b/website/src/main.jsx index c6d94af..153bd2a 100644 --- a/website/src/main.jsx +++ b/website/src/main.jsx @@ -44,57 +44,57 @@ const DEFAULT_TOOLBOX_ICON = `${TOOLBOX_ICON_BASE}toolbox.png`; const DEFAULT_SITE_CONTENT = { brand: { name: "Sokko G", homeAriaLabel: "Accueil Sokko G" }, navigation: { home: "Accueil", toolboxes: "Toolboxes", games: "Jeux", mobileGames: "Infos" }, - sidebar: { badge: "Local only", note: "Données stockées dans ce navigateur." }, - topbar: { dashboard: "Dashboard", toolbox: "Toolbox active", games: "Informations jeu" }, + sidebar: { badge: "", note: "" }, + topbar: { dashboard: "", toolbox: "", games: "" }, gamesPage: { - eyebrow: "Pages informatives", - title: "Jeux disponibles", - description: "Choisissez un jeu pour consulter ses données maintenues et associer une toolbox locale.", + eyebrow: "", + title: "Jeux", + description: "", emptyTitle: "Aucun jeu disponible", - emptyText: "Ajoutez des entrées dans /data/games.json." + emptyText: "" }, home: { hero: { - eyebrow: "Session gaming efficace", + eyebrow: "", title: "Sokko G", - description: "Centralisez vos outils et repères de jeu dans une interface locale, rapide à consulter, pensée pour accompagner vos sessions sans interrompre l’action.", - primaryAction: "Voir les toolboxes", - secondaryAction: "Voir les jeux" + description: "", + primaryAction: "Toolboxes", + secondaryAction: "Jeux" }, stats: { - toolboxSingular: "toolbox créée", - toolboxPlural: "toolboxs créées", - toolSingular: "outil disponible", - toolPlural: "outils disponibles" + toolboxSingular: "toolbox", + toolboxPlural: "toolboxes", + toolSingular: "outil", + toolPlural: "outils" }, origin: { - eyebrow: "Origine du nom", - title: "Pourquoi Sokko G ?", - visualAlt: "Dragon Sokko G", - dialogueAriaLabel: "Dialogue d’origine du nom Sokko G", - caption: "\"G\" c'est pour Gaming", + eyebrow: "", + title: "", + visualAlt: "", + dialogueAriaLabel: "", + caption: "", lines: [] + }, + legal: { + copyright: "", + disclaimer: "" } }, toolboxes: { - eyebrow: "Données locales", + eyebrow: "", title: "Toolboxes", newButton: "Nouvelle toolbox", - importAll: "Importer tout", - exportAll: "Exporter tout", - importOne: "Importer une toolbox", - summary: "Créez des espaces modulaires pour regrouper vos outils de session : notes, suivis, références, captures et futurs modules restent accessibles rapidement, en local, par jeu ou par usage.", + importAll: "Importer", + exportAll: "Exporter", + importOne: "Importer", + summary: "", storageHelp: { - title: "Sauvegardes locales", - text: "Vos toolboxes restent sur cet appareil, dans ce navigateur. Rien n’est envoyé sur un serveur.", - items: [ - "Un nettoyage du navigateur ou un changement d’appareil peut supprimer les données.", - "Surveillez le quota, surtout si vous ajoutez des screenshots.", - "Faites un export global régulier pour garder une sauvegarde." - ] + title: "", + text: "", + items: [] }, emptyTitle: "Aucune toolbox", - emptyText: "Créez une première toolbox pour stocker vos outils dans ce navigateur." + emptyText: "" } }; @@ -901,8 +901,8 @@ function Shell({ route, content, games, toolboxes, links, actions, children }) { {content.sidebar.badge}

{content.sidebar.note}

+ -
@@ -973,6 +973,10 @@ function HomePage({ siteContent, toolboxes }) { {content.origin.visualAlt} + ); } @@ -1012,8 +1016,8 @@ function ToolboxesPage({ siteContent, toolboxes, getToolboxGame, actions }) {
+ -
diff --git a/website/src/styles/_base.scss b/website/src/styles/_base.scss index 353065e..0417d91 100644 --- a/website/src/styles/_base.scss +++ b/website/src/styles/_base.scss @@ -296,6 +296,52 @@ input[type="checkbox"]:focus-visible { inset 0 1px 0 rgba(255, 255, 255, 0.05); } +.sidebar-action-button.primary, +.import-icon-button.primary, +.toolbox-icon-button.primary, +.card-icon-button.primary, +.drawer-action-button.primary, +.drawer-close-button.primary, +.filter-reset-button.primary, +.screenshot-viewer-button.primary, +.module-edit-button.primary, +.module-delete-button.primary, +.shot-delete-button.primary, +.tool-quick-add-button.primary { + border-color: rgba(246, 196, 83, 0.34); + background: + linear-gradient(135deg, rgba(72, 43, 160, 0.96), rgba(45, 52, 146, 0.94)) padding-box, + linear-gradient(135deg, rgba(246, 196, 83, 0.66), rgba(139, 92, 246, 0.36)) border-box; + color: #fff; + box-shadow: + 0 8px 22px rgba(5, 7, 17, 0.22), + inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.sidebar-action-button.primary:hover, +.import-icon-button.primary:hover, +.toolbox-icon-button.primary:hover, +.card-icon-button.primary:hover, +.drawer-action-button.primary:hover, +.drawer-close-button.primary:hover, +.filter-reset-button.primary:hover, +.screenshot-viewer-button.primary:hover, +.module-edit-button.primary:hover, +.module-delete-button.primary:hover, +.shot-delete-button.primary:hover, +.tool-quick-add-button.primary:hover { + border-color: rgba(246, 196, 83, 0.72); + background: + linear-gradient(135deg, rgba(109, 40, 217, 0.98), rgba(67, 56, 202, 0.96)) padding-box, + linear-gradient(135deg, rgba(246, 196, 83, 0.96), rgba(196, 181, 253, 0.68)) border-box; + color: #fff; + box-shadow: + 0 0 0 1px rgba(246, 196, 83, 0.16), + 0 0 18px rgba(139, 92, 246, 0.3), + 0 0 30px rgba(246, 196, 83, 0.16), + inset 0 1px 0 rgba(255, 255, 255, 0.18); +} + .sidebar-action-button:hover .ui-icon, .import-icon-button:hover .ui-icon, .toolbox-icon-button:hover .ui-icon, diff --git a/website/src/styles/_home.scss b/website/src/styles/_home.scss index e858a59..7c031f3 100644 --- a/website/src/styles/_home.scss +++ b/website/src/styles/_home.scss @@ -321,3 +321,19 @@ span { font-weight: 700; line-height: 1.35; } + +.home-legal { + display: grid; + gap: 6px; + margin-top: var(--space-5); + padding-top: var(--space-4); + border-top: 1px solid rgba(150, 165, 205, 0.14); + color: var(--color-text-muted); + font-size: var(--font-size-xs); + line-height: 1.45; +} + +.home-legal p { + max-width: 920px; + margin: 0; +}