diff --git a/Cargo.lock b/Cargo.lock index 3d4332a..b306375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -479,9 +479,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +checksum = "38d28fb6aa7dd680b3ffd1a3e64e115c177bdba63c0ae7f9cd48129ff05c4fa0" dependencies = [ "cmov", ] @@ -2168,9 +2168,9 @@ checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "aws-lc-rs", "ring", @@ -3235,9 +3235,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" dependencies = [ "rustls-pki-types", ] @@ -3248,14 +3248,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.9", + "webpki-roots 1.0.11", ] [[package]] name = "webpki-roots" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" dependencies = [ "rustls-pki-types", ] diff --git a/docs/roadmap.md b/docs/roadmap.md index b6ff7a0..209e35f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -139,6 +139,7 @@ Reportés : file d’attente des fusions hors connexion (supprimée, ADR 0041), | Rareté des mots (ADR 0040, proposé) : plus haute rareté des briques de la recette la moins rare ; forme au codex et dans la fiche (« Rare ! », « Légendaire ! ») ; `encre_decouverte` de l’équilibrage créditée à la découverte | fait (Content : équilibrage `2026-09-28c`) ; 2 gouttes par découverte vérifiées en hors-prod | | Image J4 de l’API épinglée dans Infra (`9e48455`), *Plan* puis *Apply* horsprod ; PWA `8f0dbfe03d1c` ; catalogue `d6d91be93d05` paru | fait | | Démonstration en hors-prod (API) : 3 découvertes → jalon `jal_nde6rzt4` et pli de jalon (philo-, -iste) ; indice niveau 1 : 6 → 1 goutte ; refus `403` (encre, sabliers insuffisants), `409` (état périmé) | fait | +| Enquêtes en cours (App#88) : sous la table, une ligne repliée par carte à trouver dont un indice est acheté ; dépliée, seulement ce qui a été payé (silhouette, définition, sens littéral), jusqu’à la découverte du mot ; ni mot, ni brique, ni bouton d’achat (ADR 0051) | fait en local | | Déblocage progressif des fascicules ([ADR 0068](https://github.com/lunik/EtymoLogique/blob/master/docs/adr/0068-deblocage-progressif-des-fascicules.md)) : un nouveau joueur n’a que le premier fascicule paru ; le 2ᵉ jalon d’un fascicule débloque le suivant, aussitôt ou à sa parution ; table `fascicule_debloque` (reprise des profils déjà semés) ; graines, jalons, plis (ordinaire et offert) et quête de table limités aux fascicules débloqués ; PWA : jaquette verrouillée avec sa condition (plis, codex, quêtes), « Nouveau fascicule ouvert » sur la table, pastille Corail « nouveau » | fait en local, à déployer en hors-prod | Écarts assumés : l’annonce du jalon passe par la fiche de découverte, pas par le rituel du pli ; la feuille des indices est un dialogue sur ordinateur comme sur mobile, pas un panneau latéral ; sur un environnement à recharge courte, un sablier vaut un douzième de la recharge (1 h en production). diff --git a/web/package-lock.json b/web/package-lock.json index c5f9c8a..1050295 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1900,9 +1900,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.151.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", - "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz", + "integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==", "dev": true, "license": "MIT", "funding": { @@ -2279,9 +2279,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", - "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz", + "integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==", "cpu": [ "arm" ], @@ -2296,9 +2296,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", - "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz", + "integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==", "cpu": [ "arm64" ], @@ -2313,9 +2313,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", - "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz", + "integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==", "cpu": [ "arm64" ], @@ -2330,9 +2330,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", - "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz", + "integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==", "cpu": [ "x64" ], @@ -2347,9 +2347,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", - "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz", + "integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==", "cpu": [ "x64" ], @@ -2364,9 +2364,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", - "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz", + "integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==", "cpu": [ "arm" ], @@ -2381,9 +2381,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", - "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz", + "integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==", "cpu": [ "arm64" ], @@ -2398,9 +2398,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", - "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz", + "integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==", "cpu": [ "arm64" ], @@ -2415,9 +2415,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", - "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz", + "integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==", "cpu": [ "ppc64" ], @@ -2432,9 +2432,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", - "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz", + "integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==", "cpu": [ "s390x" ], @@ -2449,9 +2449,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", - "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz", + "integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==", "cpu": [ "x64" ], @@ -2466,9 +2466,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", - "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz", + "integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==", "cpu": [ "x64" ], @@ -2483,9 +2483,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", - "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz", + "integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==", "cpu": [ "arm64" ], @@ -2500,9 +2500,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", - "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz", + "integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==", "cpu": [ "arm64" ], @@ -2517,9 +2517,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", - "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz", + "integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==", "cpu": [ "x64" ], @@ -3246,22 +3246,22 @@ } }, "node_modules/@vitest/mocker": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.2.tgz", - "integrity": "sha512-Z5FS00Q1SJHkB35xATsmWGdQ5WA1/0MV3CDjqyv7GavHv1OfOj145MNfHOlHk7QLes21dKFDHr8EO2zvL+9WGA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.5.tgz", + "integrity": "sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.31", - "@vitest/spy": "5.0.2", + "@vitest/spy": "5.0.5", "estree-walker": "^3.0.3", - "magic-string": "^1.2.3" + "magic-string": "^1.4.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "msw": "^2.4.9", + "msw": "^2.4.9 || ^3.0.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { @@ -3274,9 +3274,9 @@ } }, "node_modules/@vitest/spy": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.2.tgz", - "integrity": "sha512-Ijc7T1nT9efNb5LxvjaBrEqw3f/QwUv5EE0nKqZxgqsaV/FxAAZ8baGylA8X/Z2oS4Lp+K74Jr6dTJsDKxJDeg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.5.tgz", + "integrity": "sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==", "dev": true, "license": "MIT", "funding": { @@ -3672,9 +3672,9 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -5670,9 +5670,9 @@ } }, "node_modules/magic-string": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz", - "integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5737,9 +5737,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.19", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", - "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -5810,9 +5810,9 @@ } }, "node_modules/obug": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", - "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -6025,9 +6025,9 @@ } }, "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "dev": true, "funding": [ { @@ -6045,9 +6045,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.18", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -6270,13 +6270,13 @@ } }, "node_modules/rolldown": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", - "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz", + "integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.151.0", + "@oxc-project/types": "=0.155.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6286,21 +6286,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.11", - "@rolldown/binding-android-arm64": "1.2.11", - "@rolldown/binding-darwin-arm64": "1.2.11", - "@rolldown/binding-darwin-x64": "1.2.11", - "@rolldown/binding-freebsd-x64": "1.2.11", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", - "@rolldown/binding-linux-arm64-gnu": "1.2.11", - "@rolldown/binding-linux-arm64-musl": "1.2.11", - "@rolldown/binding-linux-ppc64-gnu": "1.2.11", - "@rolldown/binding-linux-s390x-gnu": "1.2.11", - "@rolldown/binding-linux-x64-gnu": "1.2.11", - "@rolldown/binding-linux-x64-musl": "1.2.11", - "@rolldown/binding-openharmony-arm64": "1.2.11", - "@rolldown/binding-win32-arm64-msvc": "1.2.11", - "@rolldown/binding-win32-x64-msvc": "1.2.11" + "@rolldown/binding-android-arm-eabi": "1.2.15", + "@rolldown/binding-android-arm64": "1.2.15", + "@rolldown/binding-darwin-arm64": "1.2.15", + "@rolldown/binding-darwin-x64": "1.2.15", + "@rolldown/binding-freebsd-x64": "1.2.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.15", + "@rolldown/binding-linux-arm64-gnu": "1.2.15", + "@rolldown/binding-linux-arm64-musl": "1.2.15", + "@rolldown/binding-linux-ppc64-gnu": "1.2.15", + "@rolldown/binding-linux-s390x-gnu": "1.2.15", + "@rolldown/binding-linux-x64-gnu": "1.2.15", + "@rolldown/binding-linux-x64-musl": "1.2.15", + "@rolldown/binding-openharmony-arm64": "1.2.15", + "@rolldown/binding-win32-arm64-msvc": "1.2.15", + "@rolldown/binding-win32-x64-msvc": "1.2.15" } }, "node_modules/rollup": { @@ -6656,9 +6656,9 @@ } }, "node_modules/std-env": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", "dev": true, "license": "MIT" }, @@ -6890,9 +6890,9 @@ } }, "node_modules/tinybench": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.0.tgz", - "integrity": "sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz", + "integrity": "sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==", "dev": true, "license": "MIT", "engines": { @@ -7229,16 +7229,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", - "integrity": "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", - "postcss": "^8.5.28", - "rolldown": "~1.2.9", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "bin": { @@ -7255,7 +7255,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -7338,25 +7338,25 @@ } }, "node_modules/vitest": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.2.tgz", - "integrity": "sha512-7MQrx9pDv5aHiUcovIb/70Ys3tgtkUVgCtledvKdCmEO+/1Dicq5ZqoSxOW034m03oqC+oHOKui2dM6qtMLoJg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.5.tgz", + "integrity": "sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/mocker": "5.0.2", - "chai": "^6.2.2", + "@vitest/mocker": "5.0.5", + "chai": "^6.3.0", "es-module-lexer": "^2.3.2", "expect-type": "^1.4.0", - "magic-string": "^1.2.3", - "obug": "^2.1.4", + "magic-string": "^1.4.3", + "obug": "^3.0.0", "picomatch": "^4.0.7", - "std-env": "^4.2.0", - "tinybench": "^6.1.4", - "tinyexec": "^1.3.0", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", - "why-is-node-running": "^3.2.1" + "why-is-node-running": "3.2.1" }, "bin": { "vitest": "vitest.mjs" @@ -7371,12 +7371,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "5.0.2", - "@vitest/browser-preview": "5.0.2", + "@vitest/browser-playwright": "5.0.5", + "@vitest/browser-preview": "5.0.5", "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", - "@vitest/coverage-istanbul": "5.0.2", - "@vitest/coverage-v8": "5.0.2", - "@vitest/ui": "5.0.2", + "@vitest/coverage-istanbul": "5.0.5", + "@vitest/coverage-v8": "5.0.5", + "@vitest/ui": "5.0.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" @@ -7574,9 +7574,9 @@ } }, "node_modules/why-is-node-running": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.2.tgz", - "integrity": "sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", "dev": true, "license": "MIT", "bin": { diff --git a/web/src/App.tsx b/web/src/App.tsx index 1b79b32..62d7c99 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -4,6 +4,7 @@ import { Boutique } from './composants/Boutique' import { CompteReglages } from './composants/CompteReglages' import { Connexion } from './composants/Connexion' import { CarteSeule, Codex } from './composants/Codex' +import { Enquetes } from './composants/Enquetes' import { Indices } from './composants/Indices' import { Icone } from './composants/Icone' import { Navigation, type Onglet } from './composants/Navigation' @@ -25,7 +26,7 @@ import { memoriserRelance, memoriserSoutien, souvenirRelance, souvenirSoutien } import { usePresence } from './services/presence' import { INACTIVITE_MS, type SouvenirSoutien, ajouterTempsDeJeu, carteMasquee, doitSolliciter, estAbonne, fermerSoutien, masquerCarte } from './services/soutien' import { compteQuitte } from './services/profil' -import { indiceCourant } from './services/indices' +import { enquetesEnCours, indiceCourant } from './services/indices' import { TABLE_MIN, decouvertePossible } from './services/jeu' import { FournisseurDeblocage, useDeblocageEtat } from './services/deblocage' import { usePartie } from './services/partie' @@ -101,6 +102,7 @@ export function App({ api, catalogue }: Props) { const raisonFusion = bloquee ?? (partie.table.length < TABLE_MIN ? 'Posez deux briques pour fusionner.' : null) // Sans silhouette à indicer, l'indice n'afficherait rien : le bouton disparaît. const indiceDisponible = catalogueJeu ? indiceCourant(catalogueJeu, entrees, etat) !== null : false + const enquetes = useMemo(() => (catalogueJeu ? enquetesEnCours(catalogueJeu, entrees, etat) : []), [catalogueJeu, entrees, etat]) const bloque = catalogueJeu && etat && partie.table.length === 0 ? !decouvertePossible(catalogueJeu, etat.reserve, new Set(etat.decouvertes.map((d) => d.mot_id))) @@ -351,6 +353,7 @@ export function App({ api, catalogue }: Props) { > )} + {relance && ouvrirConnexion(quitte ? 'connexion' : 'sauvegarde')} onFermer={fermerRelance} />} {soutienOuvert && } diff --git a/web/src/composants/CarteSilhouette.tsx b/web/src/composants/CarteSilhouette.tsx new file mode 100644 index 0000000..994147d --- /dev/null +++ b/web/src/composants/CarteSilhouette.tsx @@ -0,0 +1,25 @@ +import { useEffect, useRef } from 'react' + +import type { EntreeCodex } from '../services/codex' +import { chargerSilhouette } from '../services/silhouettes' +import { bandeau } from './bandeau' + +/** La carte du codex sans texte ni bouton : même bandeau, mêmes proportions, silhouette en texture (ADR 0023). */ +export function CarteSilhouette({ entree, chemin, lireMedia }: { entree: EntreeCodex; chemin: string; lireMedia: (chemin: string) => Promise }) { + const hote = useRef(null) + useEffect(() => { + let actif = true + void chargerSilhouette(chemin, lireMedia).then((svg) => { + if (actif && svg && hote.current) hote.current.replaceChildren(svg) + }) + return () => { + actif = false + } + }, [chemin, lireMedia]) + return ( + + + + + ) +} diff --git a/web/src/composants/Enquetes.test.tsx b/web/src/composants/Enquetes.test.tsx new file mode 100644 index 0000000..7c5182f --- /dev/null +++ b/web/src/composants/Enquetes.test.tsx @@ -0,0 +1,42 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' + +import type { EntreeCodex } from '../services/codex' +import type { Enquete } from '../services/indices' +import { Enquetes } from './Enquetes' + +const lireMedia = () => Promise.resolve('') + +function enquete(n: number, obtenus: Enquete['obtenus']): Enquete { + const entree = { id: `m${n}`, n, type: 'mot', libelle: 'géologie', resultat: true, trouvee: false, legendaire: false } as EntreeCodex + return { entree, recette: { id: `r${n}`, motId: entree.id, emplacements: [] }, obtenus } +} + +describe('Enquetes', () => { + it('ne montre rien sans enquête', () => { + const { container } = render() + expect(container).toBeEmptyDOMElement() + }) + + it('résume les indices obtenus, sans jamais nommer le mot ni proposer d’achat', () => { + render( + , + ) + expect(screen.getByRole('heading', { name: 'Enquête en cours' })).toBeInTheDocument() + expect(screen.getByText('N° 011')).toBeInTheDocument() + expect(screen.getByText(/Silhouette, définition/)).toBeInTheDocument() + expect(screen.queryByText(/géologie/)).not.toBeInTheDocument() + expect(screen.queryByRole('button')).not.toBeInTheDocument() + }) + + it('replie au-delà de trois enquêtes', () => { + const liste = [1, 2, 3, 4].map((n) => enquete(n, [{ niveau: 2, titre: 'Sens littéral', contenu: { texte: 'étude de la terre' } }])) + render() + expect(screen.getAllByText(/^N° 00/)).toHaveLength(3) + fireEvent.click(screen.getByRole('button', { name: 'Voir les 4 enquêtes' })) + expect(screen.getAllByText(/^N° 00/)).toHaveLength(4) + }) +}) diff --git a/web/src/composants/Enquetes.tsx b/web/src/composants/Enquetes.tsx new file mode 100644 index 0000000..182a84e --- /dev/null +++ b/web/src/composants/Enquetes.tsx @@ -0,0 +1,74 @@ +import { useState } from 'react' + +import type { Enquete } from '../services/indices' +import { CarteSilhouette } from './CarteSilhouette' + +/** Enquêtes montrées d'emblée ; les autres se déplient d'un bouton. */ +const VISIBLES = 3 + +interface Props { + enquetes: Enquete[] + /** Lecture vérifiée d'un média du catalogue (silhouettes des cartes). */ + lireMedia: (chemin: string) => Promise +} + +const numero = (n: number) => `N° ${String(n).padStart(3, '0')}` + +/** « Silhouette, définition » : les indices obtenus, le premier avec sa majuscule. */ +function resume(enquete: Enquete): string { + return enquete.obtenus.map((o, i) => (i === 0 ? o.titre : o.titre.toLowerCase())).join(', ') +} + +/** + * Enquêtes en cours (App#88) : sous la table, une ligne repliée par carte à trouver dont un indice + * est acheté ; dépliée, ce qui a été payé, et rien d'autre (ADR 0051). Aucun bouton d'achat : + * la feuille des indices reste le seul guichet. + */ +export function Enquetes({ enquetes, lireMedia }: Props) { + const [toutes, setToutes] = useState(false) + if (enquetes.length === 0) return null + const montrees = toutes ? enquetes : enquetes.slice(0, VISIBLES) + const reste = enquetes.length - VISIBLES + return ( + + + {enquetes.length > 1 ? 'Enquêtes en cours' : 'Enquête en cours'} + {enquetes.length} + + + {montrees.map((enquete) => { + const { entree, obtenus } = enquete + const texte = [...obtenus].reverse().find((o) => o.contenu.texte) + return ( + + + + {numero(entree.n)} + · {resume(enquete)} + + {texte && {texte.niveau === 2 ? `« ${texte.contenu.texte} »` : texte.contenu.texte}} + + + {obtenus.map(({ niveau, titre, contenu }) => + contenu.silhouette ? ( + + ) : ( + + {titre} + {niveau === 3 ? {contenu.texte} : « {contenu.texte} »} + + ), + )} + + + ) + })} + + {reste > 0 && ( + setToutes(!toutes)}> + {toutes ? 'Replier' : `Voir les ${enquetes.length} enquêtes`} + + )} + + ) +} diff --git a/web/src/composants/Indices.tsx b/web/src/composants/Indices.tsx index 7bd2444..4803aaf 100644 --- a/web/src/composants/Indices.tsx +++ b/web/src/composants/Indices.tsx @@ -1,12 +1,11 @@ -import { useEffect, useRef, useState } from 'react' +import { useState } from 'react' import type { Etat, ReponseIndice } from '../contrat' import type { EntreeCodex } from '../services/codex' import { INDICES, contenuIndice, indiceCourant, indiceSuivant, indicesObtenus } from '../services/indices' import type { CatalogueJeu } from '../services/jeu' import { useEnLigne } from '../services/reseau' -import { chargerSilhouette } from '../services/silhouettes' -import { bandeau } from './bandeau' +import { CarteSilhouette } from './CarteSilhouette' import { useDialogue } from './useDialogue' interface Props { @@ -19,26 +18,6 @@ interface Props { onFermer: () => void } -/** La carte du codex sans texte ni bouton : même bandeau, mêmes proportions, silhouette en texture (ADR 0023). */ -function CarteSilhouette({ entree, chemin, lireMedia }: { entree: EntreeCodex; chemin: string; lireMedia: Props['lireMedia'] }) { - const hote = useRef(null) - useEffect(() => { - let actif = true - void chargerSilhouette(chemin, lireMedia).then((svg) => { - if (actif && svg && hote.current) hote.current.replaceChildren(svg) - }) - return () => { - actif = false - } - }, [chemin, lireMedia]) - return ( - - - - - ) -} - /** * Indices (I-12, ADR 0051) : une seule silhouette à trouver (indiceCourant), puis trois indices * (silhouette, définition, sens littéral), achetés dans cet ordre, chacun une fois contre de l'encre. diff --git a/web/src/services/indices.test.ts b/web/src/services/indices.test.ts index f652ad5..8befbe8 100644 --- a/web/src/services/indices.test.ts +++ b/web/src/services/indices.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest' import { GEO, LOGIE, RECETTE, cartes, etat, ex, jeu, manifeste } from '../test/fixtures' import { construireCodex } from './codex' import type { EntreeCodex } from './codex' -import { ciblesIndice, contenuIndice, indiceCourant, indicesDisponibles, indicesObtenus } from './indices' +import { ciblesIndice, contenuIndice, enquetesEnCours, indiceCourant, indicesDisponibles, indicesObtenus } from './indices' describe('indices', () => { const j = jeu() @@ -51,4 +51,33 @@ describe('indices', () => { expect(indiceCourant(k, entrees, etat({ reserve, indices: [{ recette_id: 'r6', niveau: 3 }] }))?.recette.id).toBe('r7') expect(indiceCourant(k, entrees, etat({ reserve: [] }))).toBeNull() }) + + describe('enquêtes en cours', () => { + const k = jeu() + k.recettes = [ + { id: 'r6', motId: 'm6', emplacements: [[GEO], [LOGIE]] }, + { id: 'r7', motId: 'm7', emplacements: [[LOGIE], [GEO]] }, + { id: 'r9', motId: 'm9', emplacements: [['absente'], [GEO]] }, + ] + const carte = (id: string, n: number, extra: Partial = {}) => + ({ id, n, resultat: true, trouvee: false, legendaire: false, definition: 'une définition', silhouette: 'a/xx.svg', ...extra }) as EntreeCodex + const entrees = [carte('m9', 9), carte('m7', 7), carte('m6', 6)] + + it('liste les cartes dont un indice est acheté, par n°, même hors de portée de la réserve', () => { + const e = etat({ reserve: [], indices: [{ recette_id: 'r9', niveau: 1 }, { recette_id: 'r7', niveau: 3 }, { recette_id: 'r7', niveau: 1 }] }) + const liste = enquetesEnCours(k, entrees, e) + expect(liste.map((x) => x.entree.n)).toEqual([7, 9]) + // Dans l'ordre d'affichage, et seulement ce qui est payé. + expect(liste[0]!.obtenus.map((o) => o.niveau)).toEqual([1, 3]) + expect(liste[1]!.obtenus).toEqual([{ niveau: 1, titre: 'Silhouette', contenu: { silhouette: 'a/xx.svg' } }]) + }) + + it('oublie une carte trouvée, une légendaire et une carte sans indice', () => { + const indices = [{ recette_id: 'r6', niveau: 1 }, { recette_id: 'r7', niveau: 1 }] + const liste = enquetesEnCours(k, [carte('m6', 6, { trouvee: true }), carte('m7', 7, { legendaire: true }), carte('m9', 9)], etat({ indices })) + expect(liste).toEqual([]) + expect(enquetesEnCours(k, entrees, etat())).toEqual([]) + expect(enquetesEnCours(k, entrees, null)).toEqual([]) + }) + }) }) diff --git a/web/src/services/indices.ts b/web/src/services/indices.ts index d652675..cf489ff 100644 --- a/web/src/services/indices.ts +++ b/web/src/services/indices.ts @@ -75,3 +75,32 @@ export function indicesDisponibles(jeu: CatalogueJeu, entree: EntreeCodex): numb export function indiceSuivant(jeu: CatalogueJeu, entree: EntreeCodex, obtenus: Set): number | undefined { return indicesDisponibles(jeu, entree).find((n) => !obtenus.has(n)) } + +/** Une enquête : une carte à trouver dont au moins un indice est acheté, et ce qui l'a été. */ +export interface Enquete { + entree: EntreeCodex + recette: RecetteJeu + /** Indices obtenus, dans l'ordre d'affichage (`INDICES`), avec leur contenu. */ + obtenus: { niveau: number; titre: string; contenu: ContenuIndice }[] +} + +/** + * Enquêtes en cours (App#88) : les cartes à trouver dont le joueur a acheté au moins un indice, + * par n° croissant, jusqu'à la découverte du mot. Seul ce qui a été payé s'y lit (ADR 0051) : + * ni le mot, ni ses briques, ni s'il se forme avec la réserve. Jamais une légendaire ; un + * fascicule verrouillé n'a déjà plus de silhouette dans le codex. + */ +export function enquetesEnCours(jeu: CatalogueJeu, entrees: EntreeCodex[], etat: Etat | null): Enquete[] { + if (!etat?.indices?.length) return [] + return ciblesIndice(jeu, entrees) + .map(({ entree, recette }) => { + const achetes = indicesObtenus(etat, recette.id) + const obtenus = INDICES.filter((i) => achetes.has(i.niveau)).flatMap(({ niveau, titre }) => { + const contenu = contenuIndice(jeu, entree, niveau) + return contenu ? [{ niveau, titre, contenu }] : [] + }) + return { entree, recette, obtenus } + }) + .filter((e) => e.obtenus.length > 0) + .sort((a, b) => a.entree.n - b.entree.n) +} diff --git a/web/src/styles.css b/web/src/styles.css index 4cc0708..fb2a2e5 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -817,6 +817,26 @@ button.disc { font: inherit; cursor: pointer; } .indice-contenu { grid-column: 1 / -1; display: grid; gap: 8px; justify-items: start; padding-top: 10px; border-top: 1.5px solid var(--line); } /* La carte-silhouette reprend `.mini` du codex (codex.css) ; seule sa taille et son inertie changent. */ .indice-carte { justify-self: center; width: 132px; cursor: default; pointer-events: none; } + +/* Enquêtes en cours (App#88) : sous la table, une ligne repliée par carte dont un indice est acheté. */ +.enquetes .panel-head { margin-bottom: 0; } +.enquetes-liste { display: grid; gap: 8px; } +.enquete { border: 1.5px solid var(--line-strong); border-radius: 12px; background: var(--paper); } +.enquete[open] { border-color: var(--fixed-ink); background: var(--cream); } +.enquete summary { display: grid; gap: 4px; min-height: 44px; padding: 10px 40px 10px 12px; position: relative; cursor: pointer; list-style: none; } +.enquete summary::-webkit-details-marker { display: none; } +.enquete summary::after { position: absolute; top: 14px; right: 14px; width: 8px; height: 8px; border-right: 2px solid var(--ink-3); border-bottom: 2px solid var(--ink-3); transform: rotate(45deg); content: ""; } +.enquete[open] summary::after { top: 18px; transform: rotate(-135deg); } +.enquete summary:focus-visible { outline: 2px solid var(--c-action-deep); outline-offset: 2px; border-radius: 10px; } +.enquete-ligne { font-size: 14px; } +.enquete-num { font-family: var(--mono); font-size: 12px; font-weight: 700; } +.enquete-obtenus { color: var(--ink-2); } +.enquete-apercu { overflow: hidden; color: var(--ink-3); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } +.enquete[open] .enquete-apercu { display: none; } +.enquete-corps { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px 16px; padding: 12px; border-top: 1.5px solid var(--line); } +.enquete-corps .indice-carte { justify-self: auto; width: 104px; } +.enquete-texte { display: grid; flex: 1 1 200px; gap: 6px; min-width: 0; } +.enquetes .panel-link { margin-top: 0; align-self: flex-start; min-height: 44px; } /* Prix en gouttes : la goutte au trait, de la couleur du texte du bouton. */ .btn-prix { gap: 6px; min-width: 72px; padding: 8px 12px; font-variant-numeric: tabular-nums; } .btn-prix .prix { min-width: 2ch; text-align: right; }
{contenu.texte}
« {contenu.texte} »