feat: optimize images - convert PNG/JPG to WebP (93% size reduction, 42.7MB saved)
515
package-lock.json
generated
@ -76,6 +76,7 @@
|
||||
"globals": "^15.15.0",
|
||||
"lovable-tagger": "^1.1.10",
|
||||
"postcss": "^8.5.6",
|
||||
"sharp": "^0.34.4",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.38.0",
|
||||
@ -153,6 +154,17 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
|
||||
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
@ -845,6 +857,456 @@
|
||||
"url": "https://github.com/sponsors/nzakas"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
|
||||
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz",
|
||||
"integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz",
|
||||
"integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz",
|
||||
"integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz",
|
||||
"integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz",
|
||||
"integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz",
|
||||
"integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz",
|
||||
"integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz",
|
||||
"integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz",
|
||||
"integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz",
|
||||
"integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz",
|
||||
"integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz",
|
||||
"integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz",
|
||||
"integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz",
|
||||
"integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz",
|
||||
"integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz",
|
||||
"integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz",
|
||||
"integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz",
|
||||
"integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz",
|
||||
"integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz",
|
||||
"integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz",
|
||||
"integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz",
|
||||
"integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@ -4282,6 +4744,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz",
|
||||
"integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
@ -6567,6 +7039,49 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.4",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz",
|
||||
"integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.0.0",
|
||||
"detect-libc": "^2.1.0",
|
||||
"semver": "^7.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.34.4",
|
||||
"@img/sharp-darwin-x64": "0.34.4",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.3",
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.3",
|
||||
"@img/sharp-libvips-linux-arm": "1.2.3",
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.3",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.3",
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.3",
|
||||
"@img/sharp-libvips-linux-x64": "1.2.3",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.3",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.3",
|
||||
"@img/sharp-linux-arm": "0.34.4",
|
||||
"@img/sharp-linux-arm64": "0.34.4",
|
||||
"@img/sharp-linux-ppc64": "0.34.4",
|
||||
"@img/sharp-linux-s390x": "0.34.4",
|
||||
"@img/sharp-linux-x64": "0.34.4",
|
||||
"@img/sharp-linuxmusl-arm64": "0.34.4",
|
||||
"@img/sharp-linuxmusl-x64": "0.34.4",
|
||||
"@img/sharp-wasm32": "0.34.4",
|
||||
"@img/sharp-win32-arm64": "0.34.4",
|
||||
"@img/sharp-win32-ia32": "0.34.4",
|
||||
"@img/sharp-win32-x64": "0.34.4"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
"build": "vite build",
|
||||
"build:dev": "vite build --mode development",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"optimize-images": "node scripts/optimize-images.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
@ -79,6 +80,7 @@
|
||||
"globals": "^15.15.0",
|
||||
"lovable-tagger": "^1.1.10",
|
||||
"postcss": "^8.5.6",
|
||||
"sharp": "^0.34.4",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.38.0",
|
||||
|
||||
BIN
public/images/projects/cloud_1.webp
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
public/images/projects/cub3d.webp
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
public/images/projects/homemade_nas.webp
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
public/images/projects/minishell.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/images/projects/pong.webp
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
public/images/sites/avopieces/mookup/3-devices-black (1).webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/images/sites/avopieces/mookup/3-devices-white (1).webp
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
public/images/sites/avopieces/mookup/desktop (1).webp
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/images/sites/avopieces/mookup/laptop (1).webp
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
public/images/sites/avopieces/mookup/mobile-black (1).webp
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/images/sites/avopieces/mookup/mobile-white (1).webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/images/sites/avopieces/mookup/tablet-black (1).webp
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
public/images/sites/avopieces/mookup/tablet-white (1).webp
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
public/images/sites/avopieces/pc.webp
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
public/images/sites/avopieces/tablette.webp
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/images/sites/avopieces/tel.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/images/sites/etsidemain/mookup/3-devices-black.webp
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/images/sites/etsidemain/mookup/3-devices-white.webp
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
public/images/sites/etsidemain/mookup/desktop.webp
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
public/images/sites/etsidemain/mookup/laptop.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/images/sites/etsidemain/mookup/mobile-black.webp
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
public/images/sites/etsidemain/mookup/mobile-white.webp
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/sites/etsidemain/mookup/tablet-black.webp
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
public/images/sites/etsidemain/mookup/tablet-white.webp
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
public/images/sites/etsidemain/pc.webp
Normal file
|
After Width: | Height: | Size: 243 KiB |
BIN
public/images/sites/etsidemain/tablette.webp
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
public/images/sites/etsidemain/tel.webp
Normal file
|
After Width: | Height: | Size: 105 KiB |
50
scripts/cleanup-old-images.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { unlink } from 'fs/promises';
|
||||
import { readdir, stat } from 'fs/promises';
|
||||
import { join, extname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const publicDir = join(__dirname, '..', 'public', 'images');
|
||||
|
||||
async function getAllImageFiles(dir) {
|
||||
const files = [];
|
||||
const items = await readdir(dir);
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = join(dir, item);
|
||||
const itemStat = await stat(fullPath);
|
||||
|
||||
if (itemStat.isDirectory()) {
|
||||
files.push(...(await getAllImageFiles(fullPath)));
|
||||
} else if (['.png', '.jpg', '.jpeg'].includes(extname(item).toLowerCase())) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🗑️ Suppression des anciennes images PNG/JPG...\n');
|
||||
|
||||
const imageFiles = await getAllImageFiles(publicDir);
|
||||
console.log(`📊 ${imageFiles.length} images à supprimer\n`);
|
||||
|
||||
let deletedCount = 0;
|
||||
for (const file of imageFiles) {
|
||||
try {
|
||||
await unlink(file);
|
||||
console.log(`✅ Supprimé: ${file}`);
|
||||
deletedCount++;
|
||||
} catch (error) {
|
||||
console.error(`❌ Erreur avec ${file}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n✨ ${deletedCount} image(s) supprimée(s)!`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
88
scripts/optimize-images.js
Normal file
@ -0,0 +1,88 @@
|
||||
import sharp from 'sharp';
|
||||
import { readdir, stat } from 'fs/promises';
|
||||
import { join, extname, dirname, basename } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const publicDir = join(__dirname, '..', 'public', 'images');
|
||||
|
||||
async function getAllImageFiles(dir) {
|
||||
const files = [];
|
||||
const items = await readdir(dir);
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = join(dir, item);
|
||||
const itemStat = await stat(fullPath);
|
||||
|
||||
if (itemStat.isDirectory()) {
|
||||
files.push(...(await getAllImageFiles(fullPath)));
|
||||
} else if (['.png', '.jpg', '.jpeg'].includes(extname(item).toLowerCase())) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
async function optimizeImage(filePath) {
|
||||
const ext = extname(filePath).toLowerCase();
|
||||
const dir = dirname(filePath);
|
||||
const name = basename(filePath, ext);
|
||||
const webpPath = join(dir, `${name}.webp`);
|
||||
|
||||
try {
|
||||
const stats = await stat(filePath);
|
||||
const originalSize = stats.size;
|
||||
|
||||
// Convertir en WebP avec optimisation
|
||||
await sharp(filePath)
|
||||
.webp({
|
||||
quality: 85,
|
||||
effort: 6
|
||||
})
|
||||
.toFile(webpPath);
|
||||
|
||||
const newStats = await stat(webpPath);
|
||||
const newSize = newStats.size;
|
||||
const reduction = ((originalSize - newSize) / originalSize * 100).toFixed(2);
|
||||
|
||||
console.log(`✅ ${basename(filePath)}`);
|
||||
console.log(` Original: ${(originalSize / 1024).toFixed(2)} KB`);
|
||||
console.log(` WebP: ${(newSize / 1024).toFixed(2)} KB`);
|
||||
console.log(` Réduction: ${reduction}%\n`);
|
||||
|
||||
return { originalSize, newSize };
|
||||
} catch (error) {
|
||||
console.error(`❌ Erreur avec ${filePath}:`, error.message);
|
||||
return { originalSize: 0, newSize: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🖼️ Optimisation des images...\n');
|
||||
|
||||
const imageFiles = await getAllImageFiles(publicDir);
|
||||
console.log(`📊 ${imageFiles.length} images trouvées\n`);
|
||||
|
||||
let totalOriginal = 0;
|
||||
let totalNew = 0;
|
||||
|
||||
for (const file of imageFiles) {
|
||||
const { originalSize, newSize } = await optimizeImage(file);
|
||||
totalOriginal += originalSize;
|
||||
totalNew += newSize;
|
||||
}
|
||||
|
||||
const totalReduction = ((totalOriginal - totalNew) / totalOriginal * 100).toFixed(2);
|
||||
|
||||
console.log('=' .repeat(50));
|
||||
console.log(`📊 RÉSUMÉ TOTAL:`);
|
||||
console.log(` Taille originale: ${(totalOriginal / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(` Taille optimisée: ${(totalNew / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(` Économie totale: ${totalReduction}%`);
|
||||
console.log(` ${(totalOriginal - totalNew) / 1024 / 1024} MB économisés! 🎉`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
44
scripts/update-image-references.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
const filesToUpdate = [
|
||||
'src/components/sections/ProjectsSection.tsx',
|
||||
'src/data/projects.ts'
|
||||
];
|
||||
|
||||
async function updateFile(filePath) {
|
||||
try {
|
||||
let content = await readFile(filePath, 'utf-8');
|
||||
const originalContent = content;
|
||||
|
||||
// Remplacer .png, .jpg, .jpeg par .webp
|
||||
content = content.replace(/\.(png|jpg|jpeg)"/g, '.webp"');
|
||||
content = content.replace(/\.(png|jpg|jpeg)'/g, ".webp'");
|
||||
|
||||
if (content !== originalContent) {
|
||||
await writeFile(filePath, content, 'utf-8');
|
||||
console.log(`✅ ${filePath} mis à jour`);
|
||||
return true;
|
||||
} else {
|
||||
console.log(`⏭️ ${filePath} - aucun changement nécessaire`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Erreur avec ${filePath}:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🔄 Mise à jour des références d\'images...\n');
|
||||
|
||||
let updatedCount = 0;
|
||||
for (const file of filesToUpdate) {
|
||||
const updated = await updateFile(file);
|
||||
if (updated) updatedCount++;
|
||||
}
|
||||
|
||||
console.log(`\n✨ ${updatedCount} fichier(s) mis à jour!`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@ -11,37 +11,37 @@ export const ProjectsSection = () => {
|
||||
{
|
||||
key: "avopieces",
|
||||
icon: <Scale className="w-6 h-6 text-primary" />,
|
||||
image: "/images/sites/avopieces/mookup/3-devices-white (1).png",
|
||||
image: "/images/sites/avopieces/mookup/3-devices-white (1).webp",
|
||||
},
|
||||
{
|
||||
key: "nas",
|
||||
icon: <Server className="w-6 h-6 text-primary" />,
|
||||
image: "/images/projects/homemade_nas.png",
|
||||
image: "/images/projects/homemade_nas.webp",
|
||||
},
|
||||
{
|
||||
key: "transcendence",
|
||||
icon: <Gamepad2 className="w-6 h-6 text-primary" />,
|
||||
image: "/images/projects/pong.png",
|
||||
image: "/images/projects/pong.webp",
|
||||
},
|
||||
{
|
||||
key: "cloud",
|
||||
icon: <Cloud className="w-6 h-6 text-primary" />,
|
||||
image: "/images/projects/cloud_1.png",
|
||||
image: "/images/projects/cloud_1.webp",
|
||||
},
|
||||
{
|
||||
key: "minishell",
|
||||
icon: <Terminal className="w-6 h-6 text-primary" />,
|
||||
image: "/images/projects/minishell.png",
|
||||
image: "/images/projects/minishell.webp",
|
||||
},
|
||||
{
|
||||
key: "etsidemain",
|
||||
icon: <Globe className="w-6 h-6 text-primary" />,
|
||||
image: "/images/sites/etsidemain/mookup/3-devices-white.png",
|
||||
image: "/images/sites/etsidemain/mookup/3-devices-white.webp",
|
||||
},
|
||||
{
|
||||
key: "cube3d",
|
||||
icon: <Box className="w-6 h-6 text-primary" />,
|
||||
image: "/images/projects/cub3d.png",
|
||||
image: "/images/projects/cub3d.webp",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -59,8 +59,8 @@ export const projectsData: Record<string, ProjectData> = {
|
||||
fr: "Architecture microservices avec Docker Compose, reverse proxy Traefik automatisant les certificats Let's Encrypt, système de backup automatique avec rsync, et monitoring avec Prometheus.",
|
||||
en: "Microservices architecture with Docker Compose, Traefik reverse proxy automating Let's Encrypt certificates, automatic backup system with rsync, and monitoring with Prometheus.",
|
||||
},
|
||||
mainImage: "/images/projects/homemade_nas.png",
|
||||
images: ["/images/projects/homemade_nas.png"],
|
||||
mainImage: "/images/projects/homemade_nas.webp",
|
||||
images: ["/images/projects/homemade_nas.webp"],
|
||||
technologies: [
|
||||
{ name: "OpenMediaVault", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/debian/debian-original.svg" },
|
||||
{ name: "Docker", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/docker/docker-original.svg" },
|
||||
@ -113,8 +113,8 @@ export const projectsData: Record<string, ProjectData> = {
|
||||
fr: "Architecture MVC avec NestJS, WebSocket rooms pour les matchs, système de queuing Redis pour le matchmaking, JWT pour l'authentification, et Canvas HTML5 pour le rendu du jeu.",
|
||||
en: "MVC architecture with NestJS, WebSocket rooms for matches, Redis queuing system for matchmaking, JWT for authentication, and HTML5 Canvas for game rendering.",
|
||||
},
|
||||
mainImage: "/images/projects/pong.png",
|
||||
images: ["/images/projects/pong.png"],
|
||||
mainImage: "/images/projects/pong.webp",
|
||||
images: ["/images/projects/pong.webp"],
|
||||
technologies: [
|
||||
{ name: "React", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/react/react-original.svg" },
|
||||
{ name: "TypeScript", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/typescript/typescript-original.svg" },
|
||||
@ -167,8 +167,8 @@ export const projectsData: Record<string, ProjectData> = {
|
||||
fr: "Docker Compose pour l'orchestration, Ansible playbooks pour l'automatisation, volumes Docker pour la persistance, et réseau bridge personnalisé pour l'isolation.",
|
||||
en: "Docker Compose for orchestration, Ansible playbooks for automation, Docker volumes for persistence, and custom bridge network for isolation.",
|
||||
},
|
||||
mainImage: "/images/projects/cloud_1.png",
|
||||
images: ["/images/projects/cloud_1.png"],
|
||||
mainImage: "/images/projects/cloud_1.webp",
|
||||
images: ["/images/projects/cloud_1.webp"],
|
||||
technologies: [
|
||||
{ name: "Docker", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/docker/docker-original.svg" },
|
||||
{ name: "Ansible", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/ansible/ansible-original.svg" },
|
||||
@ -220,8 +220,8 @@ export const projectsData: Record<string, ProjectData> = {
|
||||
fr: "Tokenizer/Lexer pour le parsing, AST pour représenter les commandes, gestion des descripteurs de fichiers pour les redirections, et table de hash pour les variables d'environnement.",
|
||||
en: "Tokenizer/Lexer for parsing, AST to represent commands, file descriptor management for redirections, and hash table for environment variables.",
|
||||
},
|
||||
mainImage: "/images/projects/minishell.png",
|
||||
images: ["/images/projects/minishell.png"],
|
||||
mainImage: "/images/projects/minishell.webp",
|
||||
images: ["/images/projects/minishell.webp"],
|
||||
technologies: [
|
||||
{ name: "C", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/c/c-original.svg" },
|
||||
{ name: "Linux", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/linux/linux-original.svg" },
|
||||
@ -272,8 +272,8 @@ export const projectsData: Record<string, ProjectData> = {
|
||||
fr: "Algorithme DDA pour le raycasting, lookup tables pour les calculs trigonométriques, buffer d'image pour le double buffering, et grille 2D pour la détection de collisions.",
|
||||
en: "DDA algorithm for raycasting, lookup tables for trigonometric calculations, image buffer for double buffering, and 2D grid for collision detection.",
|
||||
},
|
||||
mainImage: "/images/projects/cub3d.png",
|
||||
images: ["/images/projects/cub3d.png"],
|
||||
mainImage: "/images/projects/cub3d.webp",
|
||||
images: ["/images/projects/cub3d.webp"],
|
||||
technologies: [
|
||||
{ name: "C", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/c/c-original.svg" },
|
||||
{ name: "MiniLibX", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/c/c-original.svg" },
|
||||
@ -324,16 +324,16 @@ export const projectsData: Record<string, ProjectData> = {
|
||||
fr: "HTML5/CSS3 sémantique, animations avec transitions CSS et Intersection Observer, balises meta optimisées, structure de données JSON-LD, et optimisation des assets.",
|
||||
en: "Semantic HTML5/CSS3, animations with CSS transitions and Intersection Observer, optimized meta tags, JSON-LD data structure, and asset optimization.",
|
||||
},
|
||||
mainImage: "/images/sites/etsidemain/mookup/3-devices-white.png",
|
||||
mainImage: "/images/sites/etsidemain/mookup/3-devices-white.webp",
|
||||
images: [
|
||||
"/images/sites/etsidemain/mookup/3-devices-white.png",
|
||||
"/images/sites/etsidemain/mookup/desktop.png",
|
||||
"/images/sites/etsidemain/mookup/laptop.png",
|
||||
"/images/sites/etsidemain/mookup/tablet-white.png",
|
||||
"/images/sites/etsidemain/mookup/mobile-white.png",
|
||||
"/images/sites/etsidemain/pc.png",
|
||||
"/images/sites/etsidemain/tablette.png",
|
||||
"/images/sites/etsidemain/tel.png"
|
||||
"/images/sites/etsidemain/mookup/3-devices-white.webp",
|
||||
"/images/sites/etsidemain/mookup/desktop.webp",
|
||||
"/images/sites/etsidemain/mookup/laptop.webp",
|
||||
"/images/sites/etsidemain/mookup/tablet-white.webp",
|
||||
"/images/sites/etsidemain/mookup/mobile-white.webp",
|
||||
"/images/sites/etsidemain/pc.webp",
|
||||
"/images/sites/etsidemain/tablette.webp",
|
||||
"/images/sites/etsidemain/tel.webp"
|
||||
],
|
||||
technologies: [
|
||||
{ name: "HTML5", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/html5/html5-original.svg" },
|
||||
@ -386,16 +386,16 @@ export const projectsData: Record<string, ProjectData> = {
|
||||
fr: "Architecture MERN stack, RAG (Retrieval Augmented Generation) pour le chatbot, chiffrement des données, système de roles et permissions, API RESTful, et design system cohérent.",
|
||||
en: "MERN stack architecture, RAG (Retrieval Augmented Generation) for chatbot, data encryption, roles and permissions system, RESTful API, and consistent design system.",
|
||||
},
|
||||
mainImage: "/images/sites/avopieces/mookup/3-devices-white (1).png",
|
||||
mainImage: "/images/sites/avopieces/mookup/3-devices-white (1).webp",
|
||||
images: [
|
||||
"/images/sites/avopieces/mookup/3-devices-white (1).png",
|
||||
"/images/sites/avopieces/mookup/desktop (1).png",
|
||||
"/images/sites/avopieces/mookup/laptop (1).png",
|
||||
"/images/sites/avopieces/mookup/tablet-white (1).png",
|
||||
"/images/sites/avopieces/mookup/mobile-white (1).png",
|
||||
"/images/sites/avopieces/pc.png",
|
||||
"/images/sites/avopieces/tablette.png",
|
||||
"/images/sites/avopieces/tel.png"
|
||||
"/images/sites/avopieces/mookup/3-devices-white (1).webp",
|
||||
"/images/sites/avopieces/mookup/desktop (1).webp",
|
||||
"/images/sites/avopieces/mookup/laptop (1).webp",
|
||||
"/images/sites/avopieces/mookup/tablet-white (1).webp",
|
||||
"/images/sites/avopieces/mookup/mobile-white (1).webp",
|
||||
"/images/sites/avopieces/pc.webp",
|
||||
"/images/sites/avopieces/tablette.webp",
|
||||
"/images/sites/avopieces/tel.webp"
|
||||
],
|
||||
technologies: [
|
||||
{ name: "React", iconUrl: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/react/react-original.svg" },
|
||||
|
||||