fix(app): proxy photo opaque + EXIF strip + WebP (C4), radius_eff communes immenses (A1), variante no-frwiki (A3), cache wiki 24h
This commit is contained in:
Generated
+607
-1
@@ -8,18 +8,30 @@
|
||||
"name": "jwe",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"maplibre-gl": "^5.1.0"
|
||||
"maplibre-gl": "^5.1.0",
|
||||
"sharp": "^0.35.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.2.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@types/node": "^26.1.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.1.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vite": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz",
|
||||
"integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
@@ -462,6 +474,506 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
|
||||
"integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
|
||||
"integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-freebsd-wasm32": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
|
||||
"integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
|
||||
"integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
|
||||
"integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
|
||||
"integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
|
||||
"integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
|
||||
"integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
|
||||
"integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
|
||||
"integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
|
||||
"integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
|
||||
"integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
|
||||
"integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
|
||||
"integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
|
||||
"integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
|
||||
"integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-riscv64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
|
||||
"integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
|
||||
"integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
|
||||
"integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
|
||||
"integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
|
||||
"integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
|
||||
"integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.11.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-webcontainers-wasm32": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
|
||||
"integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
|
||||
"integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
|
||||
"integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
|
||||
"integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
@@ -1246,6 +1758,16 @@
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
|
||||
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
@@ -1364,6 +1886,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.8.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz",
|
||||
@@ -1879,6 +2410,18 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz",
|
||||
@@ -1886,6 +2429,55 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
|
||||
"integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.1.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.35.3",
|
||||
"@img/sharp-darwin-x64": "0.35.3",
|
||||
"@img/sharp-freebsd-wasm32": "0.35.3",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.2",
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.2",
|
||||
"@img/sharp-libvips-linux-arm": "1.3.2",
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.2",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.2",
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.2",
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.2",
|
||||
"@img/sharp-libvips-linux-x64": "1.3.2",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.2",
|
||||
"@img/sharp-linux-arm": "0.35.3",
|
||||
"@img/sharp-linux-arm64": "0.35.3",
|
||||
"@img/sharp-linux-ppc64": "0.35.3",
|
||||
"@img/sharp-linux-riscv64": "0.35.3",
|
||||
"@img/sharp-linux-s390x": "0.35.3",
|
||||
"@img/sharp-linux-x64": "0.35.3",
|
||||
"@img/sharp-linuxmusl-arm64": "0.35.3",
|
||||
"@img/sharp-linuxmusl-x64": "0.35.3",
|
||||
"@img/sharp-webcontainers-wasm32": "0.35.3",
|
||||
"@img/sharp-win32-arm64": "0.35.3",
|
||||
"@img/sharp-win32-ia32": "0.35.3",
|
||||
"@img/sharp-win32-x64": "0.35.3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/sirv": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
|
||||
@@ -2020,6 +2612,13 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
@@ -2034,6 +2633,13 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.4.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
|
||||
|
||||
+3
-1
@@ -14,12 +14,14 @@
|
||||
"@sveltejs/adapter-node": "^5.2.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@types/node": "^26.1.1",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.1.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vite": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"maplibre-gl": "^5.1.0"
|
||||
"maplibre-gl": "^5.1.0",
|
||||
"sharp": "^0.35.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,26 @@
|
||||
}
|
||||
|
||||
let { photo, revealed = false }: Props = $props();
|
||||
|
||||
// Proxy /api/photo : 3 largeurs disponibles, le navigateur choisit (C4).
|
||||
const srcset = $derived(
|
||||
photo.srcset
|
||||
? Object.entries(photo.srcset)
|
||||
.map(([w, u]) => `${u} ${w}w`)
|
||||
.join(', ')
|
||||
: undefined
|
||||
);
|
||||
</script>
|
||||
|
||||
<figure class="photo-panel">
|
||||
<img src={photo.url} alt={photo.alt} loading="lazy" decoding="async" />
|
||||
<img
|
||||
src={photo.url}
|
||||
{srcset}
|
||||
sizes="(min-width: 768px) 50vw, 100vw"
|
||||
alt={photo.alt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{#if revealed}
|
||||
<figcaption>{photo.credit}</figcaption>
|
||||
{/if}
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
</p>
|
||||
{#if result.distanceKm !== null}
|
||||
<p class="distance">{t().resultat.distance} : {result.distanceKm} {t().resultat.km}</p>
|
||||
{#if result.radiusEffKm !== null}
|
||||
<p class="rayon">{t().resultat.rayonScore} : {result.radiusEffKm} {t().resultat.km}</p>
|
||||
{/if}
|
||||
{/if}
|
||||
<p class="lieu">
|
||||
<strong>{result.nom}</strong>
|
||||
@@ -91,6 +94,12 @@
|
||||
color: var(--oki-encre-doux);
|
||||
}
|
||||
|
||||
.rayon {
|
||||
margin: 0.1rem 0 0;
|
||||
font-size: 0.82rem;
|
||||
color: var(--oki-encre-doux);
|
||||
}
|
||||
|
||||
.credit-photo {
|
||||
margin: 0;
|
||||
font-size: 0.72rem;
|
||||
|
||||
@@ -12,8 +12,11 @@
|
||||
|
||||
let { nom, wikidataId, tonPositif, tonNeutre }: Props = $props();
|
||||
|
||||
// undefined = chargement, null = pas de fiche (Cas B), WikiSummary = Cas A
|
||||
// undefined = chargement, null = pas de fiche, WikiSummary = Cas A
|
||||
let wiki = $state<WikiSummary | null | undefined>(undefined);
|
||||
// Cas intermédiaire (audit A3) : QID présent, fiche Wikidata existante,
|
||||
// mais pas d'article Wikipédia en français (404 {reason:'no-frwiki'}).
|
||||
let sansFrwiki = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
if (!wikidataId) {
|
||||
@@ -25,7 +28,19 @@
|
||||
try {
|
||||
const res = await fetch(`/api/wiki/${wikidataId}`);
|
||||
if (annule) return;
|
||||
wiki = res.ok ? ((await res.json()) as WikiSummary) : null;
|
||||
if (res.ok) {
|
||||
wiki = (await res.json()) as WikiSummary;
|
||||
} else {
|
||||
if (res.status === 404) {
|
||||
try {
|
||||
const corps = (await res.json()) as { reason?: string };
|
||||
sansFrwiki = corps.reason === 'no-frwiki';
|
||||
} catch {
|
||||
sansFrwiki = false;
|
||||
}
|
||||
}
|
||||
wiki = null;
|
||||
}
|
||||
} catch {
|
||||
if (!annule) wiki = null;
|
||||
}
|
||||
@@ -38,6 +53,7 @@
|
||||
const lienContribution = $derived(
|
||||
`https://fr.wikipedia.org/w/index.php?search=${encodeURIComponent(nom)}&title=Sp%C3%A9cial:Recherche`
|
||||
);
|
||||
const lienWikidata = $derived(`https://www.wikidata.org/wiki/${wikidataId}`);
|
||||
</script>
|
||||
|
||||
{#if wiki === undefined}
|
||||
@@ -61,6 +77,20 @@
|
||||
{t().resultat.lireWiki} ↗
|
||||
</a>
|
||||
</article>
|
||||
{:else if sansFrwiki}
|
||||
<!-- Variante Cas B : fiche Wikidata OK mais pas d'article FR — appel à traduire/créer -->
|
||||
<article class="panneau cas-b">
|
||||
<h2><span class="icone" aria-hidden="true">◌</span> {t().lacuneFr.titre}</h2>
|
||||
<p class="texte">{t().lacuneFr.texte}</p>
|
||||
<div class="ctas">
|
||||
<a class="btn btn-cta" href={lienContribution} target="_blank" rel="noopener noreferrer">
|
||||
{t().lacuneFr.cta} ↗
|
||||
</a>
|
||||
<a class="lien-wikidata" href={lienWikidata} target="_blank" rel="noopener noreferrer">
|
||||
{t().lacuneFr.ctaWikidata} ↗
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
{:else}
|
||||
<!-- Cas B : lacune documentaire — panneau volontairement distinct (Peak-End) -->
|
||||
<article class="panneau cas-b">
|
||||
@@ -169,6 +199,18 @@
|
||||
background: #b98a2f;
|
||||
}
|
||||
|
||||
.ctas {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
|
||||
.lien-wikidata {
|
||||
color: var(--oki-bleu);
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.guide {
|
||||
margin: 0.8rem 0 0;
|
||||
font-size: 0.88rem;
|
||||
|
||||
@@ -57,6 +57,7 @@ const fr = {
|
||||
trouve: 'Tu as trouvé ! Apprends-en plus sur ce lieu.',
|
||||
aDecouvrir: 'Le lieu était ici. Apprends-en plus sur lui.',
|
||||
lireWiki: 'Lire la suite sur Wikipédia',
|
||||
rayonScore: 'Rayon de notation',
|
||||
commune: 'Commune',
|
||||
region: 'Région',
|
||||
suivant: 'Lieu suivant',
|
||||
@@ -72,6 +73,13 @@ const fr = {
|
||||
guide: 'Besoin d’aide pour démarrer ? Suis le guide OKI « Comment contribuer à Wikipédia ».',
|
||||
guideLien: 'Ouvrir le guide de contribution'
|
||||
},
|
||||
lacuneFr: {
|
||||
titre: 'Pas encore d’article en français',
|
||||
texte:
|
||||
'La fiche Wikidata de ce lieu existe, mais personne n’a encore écrit l’article Wikipédia en français. Tu peux le traduire ou le créer : chaque article compte pour que nos territoires existent dans l’encyclopédie francophone.',
|
||||
cta: 'Traduire ou créer l’article',
|
||||
ctaWikidata: 'Voir la fiche Wikidata'
|
||||
},
|
||||
defi: {
|
||||
bilan: 'Bilan du défi',
|
||||
scoreTotal: 'Score total',
|
||||
|
||||
@@ -59,6 +59,7 @@ const gcf: Dict = {
|
||||
trouve: 'Ou trouvé ! Aprann pli plis asou koté sa-a.',
|
||||
aDecouvrir: 'Koté-la té la. Aprann pli plis asou’y.',
|
||||
lireWiki: 'Li swit-la asou Wikipédia',
|
||||
rayonScore: 'Réyon pou pwen yo',
|
||||
commune: 'Komin',
|
||||
region: 'Péyi',
|
||||
suivant: 'Koté swivan',
|
||||
@@ -74,6 +75,13 @@ const gcf: Dict = {
|
||||
guide: 'Ou bizwen èd pou koumansé ? Swiv gid OKI « Kijan pou kontribyé sou Wikipédia ».',
|
||||
guideLien: 'Ouvri gid kontribisyon-an'
|
||||
},
|
||||
lacuneFr: {
|
||||
titre: 'Pwen blindé — Pa nyen an fransé',
|
||||
texte:
|
||||
'Fich Wikidata koté sa-a égzisté, mé pésonn pòkò ékri atik Wikipédia-la an fransé. Ou sa tradwi’y oben réyé’y : chak atik ka konté pou téritwa nou égzisté adan ansiklopedi fransé-a.',
|
||||
cta: 'Tradwi oben réyé atik-la',
|
||||
ctaWikidata: 'Wè fich Wikidata-la'
|
||||
},
|
||||
defi: {
|
||||
bilan: 'Bilan défi-a',
|
||||
scoreTotal: 'Total pwen',
|
||||
|
||||
@@ -74,7 +74,14 @@ export function getRandomLieu(options: {
|
||||
return pool[Math.floor(Math.random() * pool.length)];
|
||||
}
|
||||
|
||||
/** Anti-triche : ne garde que les champs publiques pour /api/round. */
|
||||
/** Largeurs (px) servies par le proxy photo /api/photo/[id]?w=. */
|
||||
export const PHOTO_LARGEURS = [400, 800, 1200] as const;
|
||||
|
||||
/**
|
||||
* Anti-triche : ne garde que les champs publiques pour /api/round.
|
||||
* L'URL photo passe par le proxy serveur (/api/photo/<uuid>) : l'URL Wikimedia
|
||||
* Commons d'origine contient le nom du fichier, qui révèle souvent le lieu (C4).
|
||||
*/
|
||||
export function sanitizeRound(lieu: Lieu): RoundPlace {
|
||||
return {
|
||||
id: lieu.id,
|
||||
@@ -82,15 +89,41 @@ export function sanitizeRound(lieu: Lieu): RoundPlace {
|
||||
nom_creole: lieu.nom_creole,
|
||||
categorie: lieu.categorie,
|
||||
difficulte: lieu.difficulte,
|
||||
photo: lieu.photo,
|
||||
photo: {
|
||||
url: `/api/photo/${lieu.id}`,
|
||||
srcset: {
|
||||
'400': `/api/photo/${lieu.id}?w=400`,
|
||||
'800': `/api/photo/${lieu.id}?w=800`,
|
||||
'1200': `/api/photo/${lieu.id}?w=1200`
|
||||
},
|
||||
credit: lieu.photo.credit,
|
||||
alt: lieu.photo.alt
|
||||
},
|
||||
indices: lieu.indices
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Rayon effectif (km) de la courbe de score (audit A1).
|
||||
* Les communes guyanaises sont immenses (Saint-Laurent-du-Maroni ≈ 4 321 km²) :
|
||||
* avec le seul rayon de difficulté, un guess à quelques dizaines de km y est
|
||||
* injustement quasi nul. Quand `aire_commune_km2` est renseigné, le rayon est
|
||||
* au moins celui du disque de même aire que la commune : sqrt(aire / π).
|
||||
* Champ optionnel : sans lui, on retombe sur le rayon de difficulté.
|
||||
*/
|
||||
export function rayonEffectifKm(lieu: Lieu): number {
|
||||
const base = RAYONS[lieu.difficulte];
|
||||
const aire = lieu.aire_commune_km2;
|
||||
if (typeof aire === 'number' && Number.isFinite(aire) && aire > 0) {
|
||||
return Math.max(base, Math.sqrt(aire / Math.PI));
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoring recalculé côté serveur :
|
||||
* score = round(5000 × e^(−d/r)), r selon difficulté ;
|
||||
* pénalité −10 % par indice ; bonus temps ≤ +20 % ; total plafonné à 5000.
|
||||
* score = round(5000 × e^(−d/r_eff)) ; pénalité −10 % par indice ;
|
||||
* bonus temps ≤ +20 % ; total plafonné à 5000.
|
||||
*/
|
||||
export function scoreGuess(
|
||||
lieu: Lieu,
|
||||
@@ -99,7 +132,8 @@ export function scoreGuess(
|
||||
timeMs?: number
|
||||
): GuessResult {
|
||||
const distanceKm = haversineKm(guess, lieu.coordonnees);
|
||||
const base = Math.round(MAX_POINTS * Math.exp(-distanceKm / RAYONS[lieu.difficulte]));
|
||||
const radiusEff = rayonEffectifKm(lieu);
|
||||
const base = Math.round(MAX_POINTS * Math.exp(-distanceKm / radiusEff));
|
||||
const penalises = base * Math.max(0, 1 - 0.1 * Math.max(0, hintsUsed));
|
||||
let total = penalises;
|
||||
if (typeof timeMs === 'number' && Number.isFinite(timeMs) && timeMs >= 0) {
|
||||
@@ -109,6 +143,7 @@ export function scoreGuess(
|
||||
return {
|
||||
distanceKm: Math.round(distanceKm * 10) / 10,
|
||||
score,
|
||||
radiusEffKm: Math.round(radiusEff * 10) / 10,
|
||||
coordonnees: lieu.coordonnees,
|
||||
commune: lieu.commune,
|
||||
region: lieu.region,
|
||||
@@ -123,6 +158,7 @@ export function skipResult(lieu: Lieu): GuessResult {
|
||||
return {
|
||||
distanceKm: null,
|
||||
score: 0,
|
||||
radiusEffKm: null,
|
||||
coordonnees: lieu.coordonnees,
|
||||
commune: lieu.commune,
|
||||
region: lieu.region,
|
||||
@@ -132,10 +168,14 @@ export function skipResult(lieu: Lieu): GuessResult {
|
||||
};
|
||||
}
|
||||
|
||||
/** Catalogue Explorer : coords arrondies à 0.05° (mode sans score, fiches autorisées). */
|
||||
/**
|
||||
* Catalogue Explorer : coords arrondies à 0.05° (mode sans score, fiches autorisées).
|
||||
* L'`id` (uuid) n'est volontairement PAS exposé : il permettrait de corréler
|
||||
* l'id d'un round avec la commune du lieu (fuite de réponse, audit C4).
|
||||
* L'Explorer n'a pas besoin d'ids stables (le front utilise l'indice du tableau).
|
||||
*/
|
||||
export function catalogueExplorer(): ExplorerLieu[] {
|
||||
return lieux.map((l) => ({
|
||||
id: l.id,
|
||||
nom: l.nom,
|
||||
nom_creole: l.nom_creole,
|
||||
region: l.region,
|
||||
|
||||
+18
-2
@@ -16,6 +16,11 @@ export interface Photo {
|
||||
url: string;
|
||||
credit: string;
|
||||
alt: string;
|
||||
/**
|
||||
* URLs par largeur (px) pour `srcset`. Présent uniquement dans /api/round :
|
||||
* pointe vers le proxy /api/photo/[id]?w=… (l'URL Commons d'origine n'est jamais exposée).
|
||||
*/
|
||||
srcset?: Record<'400' | '800' | '1200', string>;
|
||||
}
|
||||
|
||||
/** Lieu complet — serveur uniquement, jamais exposé tel quel au client. */
|
||||
@@ -31,6 +36,12 @@ export interface Lieu {
|
||||
wikidata_id: string | null;
|
||||
photo: Photo;
|
||||
indices: Indice[];
|
||||
/**
|
||||
* Superficie de la commune (km²) — champ optionnel du schéma.
|
||||
* Sert à élargir le rayon de score pour les communes immenses (ex. Guyane) ;
|
||||
* le scoring doit tolérer son absence (fallback = rayon de difficulté seul).
|
||||
*/
|
||||
aire_commune_km2?: number;
|
||||
}
|
||||
|
||||
/** Lieu SANITISÉ envoyé par GET /api/round (aucune coordonnée, commune, région, wikidata_id). */
|
||||
@@ -48,6 +59,8 @@ export interface RoundPlace {
|
||||
export interface GuessResult {
|
||||
distanceKm: number | null;
|
||||
score: number;
|
||||
/** Rayon effectif utilisé par la courbe de score (km, arrondi à 0,1) — null si « Passer ». */
|
||||
radiusEffKm: number | null;
|
||||
coordonnees: Coordonnees;
|
||||
commune: string;
|
||||
region: Region;
|
||||
@@ -56,9 +69,12 @@ export interface GuessResult {
|
||||
wikidata_id: string | null;
|
||||
}
|
||||
|
||||
/** Entrée du catalogue Explorer : coords arrondies à 0.05°, mode sans score. */
|
||||
/**
|
||||
* Entrée du catalogue Explorer : coords arrondies à 0.05°, mode sans score.
|
||||
* Pas d'`id` : exposer l'uuid du lieu permettrait de corréler l'id d'un round
|
||||
* (/api/round) avec la commune/région — fuite de réponse (audit C4).
|
||||
*/
|
||||
export interface ExplorerLieu {
|
||||
id: string;
|
||||
nom: string;
|
||||
nom_creole: string;
|
||||
region: Region;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import sharp from 'sharp';
|
||||
import { getLieuById, PHOTO_LARGEURS } from '$lib/server/lieux';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
const USER_AGENT = 'JWE-OKI/2.0 (https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/JWE; contact OKI)';
|
||||
const TTL_MS = 24 * 60 * 60 * 1000; // 24 h
|
||||
|
||||
type Largeur = (typeof PHOTO_LARGEURS)[number];
|
||||
const LARGEUR_DEFAUT: Largeur = 1200;
|
||||
|
||||
/** Cache mémoire : clé `${id}:${w}`, TTL 24 h. */
|
||||
const cache = new Map<string, { expires: number; data: Buffer }>();
|
||||
/** Déduplication des fetches en vol (même photo demandée en rafale). */
|
||||
const enVol = new Map<string, Promise<Buffer | null>>();
|
||||
|
||||
/**
|
||||
* Récupère la photo via l'URL Commons stockée dans le corpus (jamais d'URL
|
||||
* fournie par le client — pas de proxy ouvert), puis la retraite avec sharp :
|
||||
* - rotate() : applique l'orientation EXIF avant suppression des métadonnées ;
|
||||
* - métadonnées : sharp ne conserve AUCUNE métadonnée (EXIF/GPS compris) sauf
|
||||
* appel explicite à withMetadata() — le WebP produit est donc propre ;
|
||||
* - conversion WebP + redimensionnement à la largeur demandée.
|
||||
*/
|
||||
async function chargerPhoto(id: string, w: Largeur): Promise<Buffer | null> {
|
||||
const lieu = getLieuById(id);
|
||||
if (!lieu) return null;
|
||||
const res = await fetch(lieu.photo.url, { headers: { 'User-Agent': USER_AGENT } });
|
||||
if (!res.ok) throw new Error(`commons ${res.status}`);
|
||||
const brut = Buffer.from(await res.arrayBuffer());
|
||||
return sharp(brut)
|
||||
.rotate()
|
||||
.resize({ width: w, withoutEnlargement: true })
|
||||
.webp({ quality: 82 })
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/photo/[id]?w=400|800|1200 → image/webp.
|
||||
* `id` = uuid du lieu (déjà exposé par /api/round). 404 si lieu inconnu.
|
||||
* L'URL ainsi produite est opaque : elle ne révèle ni le nom du fichier
|
||||
* Commons ni le lieu (audit C4).
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
const id = params.id;
|
||||
const wParam = url.searchParams.get('w');
|
||||
let w: Largeur = LARGEUR_DEFAUT;
|
||||
if (wParam !== null) {
|
||||
const n = Number(wParam);
|
||||
if (!(PHOTO_LARGEURS as readonly number[]).includes(n)) {
|
||||
return new Response('Largeur invalide (400, 800 ou 1200)', { status: 400 });
|
||||
}
|
||||
w = n as Largeur;
|
||||
}
|
||||
|
||||
const cle = `${id}:${w}`;
|
||||
const hit = cache.get(cle);
|
||||
if (hit && hit.expires > Date.now()) {
|
||||
return reponseWebp(hit.data);
|
||||
}
|
||||
|
||||
// 404 immédiat si l'id est inconnu (avant tout fetch amont).
|
||||
if (!getLieuById(id)) {
|
||||
return new Response('Lieu inconnu', { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
let promesse = enVol.get(cle);
|
||||
if (!promesse) {
|
||||
promesse = chargerPhoto(id, w).finally(() => enVol.delete(cle));
|
||||
enVol.set(cle, promesse);
|
||||
}
|
||||
const data = await promesse;
|
||||
if (!data) return new Response('Lieu inconnu', { status: 404 });
|
||||
cache.set(cle, { expires: Date.now() + TTL_MS, data });
|
||||
return reponseWebp(data);
|
||||
} catch {
|
||||
return new Response('Photo indisponible', { status: 502 });
|
||||
}
|
||||
};
|
||||
|
||||
function reponseWebp(data: Buffer): Response {
|
||||
return new Response(new Uint8Array(data), {
|
||||
headers: {
|
||||
'Content-Type': 'image/webp',
|
||||
'Cache-Control': 'public, max-age=86400, immutable'
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import type { RequestHandler } from './$types';
|
||||
|
||||
const USER_AGENT = 'JWE-OKI/2.0 (https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/JWE; contact OKI)';
|
||||
const TTL_MS = 24 * 60 * 60 * 1000; // 24 h
|
||||
const CACHE_CONTROL = 'public, max-age=86400';
|
||||
|
||||
const cache = new Map<string, { expires: number; summary: WikiSummary }>();
|
||||
|
||||
@@ -23,7 +24,7 @@ interface RestSummary {
|
||||
thumbnail?: { source?: string };
|
||||
}
|
||||
|
||||
/** GET /api/wiki/[qid] → extrait Wikipédia FR normalisé (404 si pas de sitelink fr). */
|
||||
/** GET /api/wiki/[qid] → extrait Wikipédia FR normalisé (404 {reason:'no-frwiki'} si pas de sitelink fr). */
|
||||
export const GET: RequestHandler = async ({ params, fetch }) => {
|
||||
const qid = params.qid;
|
||||
if (!/^Q\d+$/.test(qid)) {
|
||||
@@ -32,26 +33,37 @@ export const GET: RequestHandler = async ({ params, fetch }) => {
|
||||
|
||||
const cached = cache.get(qid);
|
||||
if (cached && cached.expires > Date.now()) {
|
||||
return json(cached.summary, { headers: { 'Cache-Control': 'public, max-age=3600' } });
|
||||
return json(cached.summary, { headers: { 'Cache-Control': CACHE_CONTROL } });
|
||||
}
|
||||
|
||||
const headers = { 'User-Agent': USER_AGENT, Accept: 'application/json' };
|
||||
|
||||
// 1. Wikidata : sitelink frwiki (+ libellé fr en fallback)
|
||||
// Fallback de langue : fr UNIQUEMENT, volontairement sans repli sur en —
|
||||
// l'absence d'article français devient un appel à contribution (Cas B), pas
|
||||
// une redirection silencieuse vers Wikipédia en anglais.
|
||||
const wbUrl = `https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${encodeURIComponent(qid)}&props=sitelinks%7Clabels&sitefilter=frwiki&languages=fr&format=json`;
|
||||
let entity: WbEntity | undefined;
|
||||
try {
|
||||
const res = await fetch(wbUrl, { headers });
|
||||
if (!res.ok) throw new Error(`wikidata ${res.status}`);
|
||||
entity = ((await res.json()) as WbResponse).entities?.[qid];
|
||||
// L'API résout les redirections : l'entité peut être indexée par le QID cible.
|
||||
const entities = ((await res.json()) as WbResponse).entities ?? {};
|
||||
entity = entities[qid] ?? Object.values(entities)[0];
|
||||
} catch {
|
||||
return json({ erreur: 'Wikidata injoignable' }, { status: 502 });
|
||||
}
|
||||
|
||||
const titre = entity?.sitelinks?.frwiki?.title;
|
||||
if (!titre) {
|
||||
// Pas de page Wikipédia FR → le front bascule sur l'écran « Pwen blindé ».
|
||||
return json({ erreur: 'Pas de page Wikipédia en français' }, { status: 404 });
|
||||
// QID existant mais pas d'article frwiki (cas intermédiaire, audit A3) :
|
||||
// `reason: no-frwiki` permet au front d'afficher la variante « Pwen blindé —
|
||||
// Pa nyen an fransé » (fiche Wikidata présente, article FR à traduire/créer),
|
||||
// distincte du Cas B « lacune totale » (wikidata_id null côté lieu).
|
||||
return json(
|
||||
{ reason: 'no-frwiki' },
|
||||
{ status: 404, headers: { 'Cache-Control': CACHE_CONTROL } }
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Wikipédia FR : résumé de l'article
|
||||
@@ -77,5 +89,5 @@ export const GET: RequestHandler = async ({ params, fetch }) => {
|
||||
};
|
||||
cache.set(qid, { expires: Date.now() + TTL_MS, summary });
|
||||
|
||||
return json(summary, { headers: { 'Cache-Control': 'public, max-age=3600' } });
|
||||
return json(summary, { headers: { 'Cache-Control': CACHE_CONTROL } });
|
||||
};
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
);
|
||||
const markers = $derived(
|
||||
lieuxFiltres.map((l) => ({
|
||||
id: l.id,
|
||||
// Id éphémère côté client (indice dans le catalogue) : /api/lieux n'expose
|
||||
// plus l'uuid du lieu, pour ne pas corréler un id de round avec sa commune (C4).
|
||||
id: String(lieux.indexOf(l)),
|
||||
lat: l.coordonnees.lat,
|
||||
lon: l.coordonnees.lon,
|
||||
label: `${l.nom} — ${l.commune}`
|
||||
@@ -26,7 +28,7 @@
|
||||
);
|
||||
|
||||
function ouvrir(id: string): void {
|
||||
selection = lieux.find((l) => l.id === id) ?? null;
|
||||
selection = lieux[Number(id)] ?? null;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -103,7 +105,7 @@
|
||||
<p class="localisation">
|
||||
{t().resultat.commune} : {selection.commune} — {t().regions[selection.region]}
|
||||
</p>
|
||||
{#key selection.id}
|
||||
{#key selection.nom}
|
||||
<WikiFiche nom={selection.nom} wikidataId={selection.wikidata_id} />
|
||||
{/key}
|
||||
{:else}
|
||||
|
||||
@@ -31,7 +31,9 @@ sw.addEventListener('activate', (event) => {
|
||||
function isPhoto(url: URL): boolean {
|
||||
return (
|
||||
url.hostname === 'upload.wikimedia.org' ||
|
||||
(url.hostname === 'commons.wikimedia.org' && url.pathname.startsWith('/wiki/Special:FilePath'))
|
||||
(url.hostname === 'commons.wikimedia.org' && url.pathname.startsWith('/wiki/Special:FilePath')) ||
|
||||
// Proxy photo maison (mode jeu) — réponses immutables, WebP sans métadonnées
|
||||
(url.origin === sw.location.origin && url.pathname.startsWith('/api/photo/'))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user