diff --git a/package-lock.json b/package-lock.json index 9fdab58..d447ed2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "fabricjs-react": "^2.0.0", "file-saver": "^2.0.5", "flowbite-react": "^0.10.2", + "framer-motion": "^11.11.1", "i18next": "^23.15.2", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", @@ -73,6 +74,7 @@ "typescript-eslint": "^8.8.0", "vite": "^5.4.8", "vite-plugin-compression": "^0.5.1", + "vite-plugin-minify": "^2.0.0", "vite-plugin-sitemap": "^0.7.1", "vite-plugin-tailwind-purgecss": "^0.3.3" } @@ -3822,6 +3824,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -4574,6 +4587,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/html-minifier-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-7.0.2.tgz", + "integrity": "sha512-mm2HqV22l8lFQh4r2oSsOEVea+m0qqxEmwpc9kC1p/XzmjLWrReR9D/GRs8Pex2NX/imyEH9c5IU/7tMBQCHOA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -5829,6 +5849,17 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -5983,6 +6014,19 @@ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -6675,6 +6719,17 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -7830,6 +7885,31 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.11.1.tgz", + "integrity": "sha512-Ucr9eHSrk0d+l6vyl9fvq6omh/PAWHjS+PlczpsoUdhJo1TuF3ULWJNuAMnpWQ1dGyPOyoUVuYlUKjE/s8dyCA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -8361,6 +8441,38 @@ "dev": true, "license": "MIT" }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -11563,6 +11675,16 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -11835,6 +11957,17 @@ "dev": true, "license": "MIT" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -12218,6 +12351,17 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12276,6 +12420,17 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -13843,6 +13998,16 @@ } } }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -14737,6 +14902,43 @@ "license": "ISC", "optional": true }, + "node_modules/terser": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", + "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -15354,6 +15556,20 @@ "node": ">=8" } }, + "node_modules/vite-plugin-minify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-minify/-/vite-plugin-minify-2.0.0.tgz", + "integrity": "sha512-xQWdXCip/CH3c5a0fftJtvpodOIZqp3gwfuSpGtik/W1YmZKe8WMTJrxvrjgrQ1NcP4EuqmiMCUaz8+If1CPMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^7.0.2", + "html-minifier-terser": "^7.2.0" + }, + "peerDependencies": { + "vite": "^5.4.0" + } + }, "node_modules/vite-plugin-sitemap": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/vite-plugin-sitemap/-/vite-plugin-sitemap-0.7.1.tgz", diff --git a/package.json b/package.json index c465a5f..bb46212 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "fabricjs-react": "^2.0.0", "file-saver": "^2.0.5", "flowbite-react": "^0.10.2", + "framer-motion": "^11.11.1", "i18next": "^23.15.2", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", @@ -77,6 +78,7 @@ "typescript-eslint": "^8.8.0", "vite": "^5.4.8", "vite-plugin-compression": "^0.5.1", + "vite-plugin-minify": "^2.0.0", "vite-plugin-sitemap": "^0.7.1", "vite-plugin-tailwind-purgecss": "^0.3.3" }, diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 274bc0d..7670034 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,90 +1,95 @@ { - "menu": { - "home": "Home", - "about": "About this app", - "faq": "FAQ" - }, - "title": "stegg", - "description": { - "intro": "This is a full-stack React application written in Typescript.", - "howitworks": "How it works:", - "paragraph1": "This app takes the text input from Encrypt field and writes it to the metadata of a newly created PNG file that is generated from the Canvas. The canvas image depicts the message as binary data: nodes that are filled represent 1's and nodes that are outlined represent 0's. If someone was patient enough, they could translate the message back to text just by interpreting the image as unicode values. That's why there is a toggle that deteremines whether the message is embedded with 128-bit AES encryption or as plain text.", - "paragraph2": "Once you click Save or Post, the file will be downloaded to your device or posted to the Feed, respectively. Whomever you share the image with would be able to extract and decipher the text by dragging the PNG file into the Decrypt zone and entering the passkey you provided them with, if it's encrypted. Likewise, you'll be able to decipher whichever posts you possess the password for.", - "paragraph3": "I realise that nobody asked for this, I just made it as a fun way to send secret messages to the people who visit my site alifeinbinary.com and thought it would be a fun project to share as open source for those who are wanting to learn about the technology within." - }, - "technology1": "
  • Typescript
  • React
  • HTML Canvas
  • Cryptography in the browser
  • Image generation and consumption
  • ", - "technology2": "
  • Embedding metadata in files
  • CRUD operations to a serverless API on AWS using GraphQL
  • File storage on AWS S3 managed by a cron job
  • Jest tests
  • Internationalisation
  • ", - "review": "Is this like Twitter for robots? Why would anyone make this???", - "byline": "A newly retired, former part-time receptionist and recent grandmother of twins I showed this to on the ferry", - "createdwith": "Created with", - "forkthisproject": "Star this project on ", - "likemywork": "Like my work?", - "hireme": "Hire me", - "encrypt": { - "title": "Create", - "modal": { - "title": "How to use this app", - "step1": "Type your message, click to encrypt if desired, and save/share - only those with the password can access it.", - "caveatstitle": "Caveats", - "step2": "Drop image in here and use the password given to decode the message.", - "caveatstext": "As a security measure, some operating systems, notably iOS, strip metadata from images to prevent malicious code from executing on your phone. This means that images you pass along to a third party won't be able to be decrypted if they have been sent from your iPhone.", - "securitytitle": "Security disclaimer", - "securitytext": "As a security measure, some operating systems, notably iOS, strip metadata from images to prevent malicious code from executing on your phone. This means that images you pass along to a third party won't be able to be decrypted if they have been sent from your iPhone." + "menu": { + "home": "Home", + "about": "About this app", + "faq": "FAQ" }, - "placeholder": "Type your thoughts" - }, - "decrypt": { - "title": "Decode", - "description": "

    Click to upload or drag and drop to extract message

    PNG format only

    " - }, - "downloadimagebutton": { - "tooltip": { - "title": "Download image", - "hint": "Enter a message before saving to disk" + "title": "stegg", + "description": { + "intro": "This is a full-stack React application written in Typescript.", + "howitworks": "How it works:", + "paragraph1": "This app takes the text input from Encrypt field and writes it to the metadata of a newly created PNG file that is generated from the Canvas. The canvas image depicts the message as binary data: nodes that are filled represent 1's and nodes that are outlined represent 0's. If someone was patient enough, they could translate the message back to text just by interpreting the image as unicode values. That's why there is a toggle that deteremines whether the message is embedded with 128-bit AES encryption or as plain text.", + "paragraph2": "Once you click Save or Post, the file will be downloaded to your device or posted to the Feed, respectively. Whomever you share the image with would be able to extract and decipher the text by dragging the PNG file into the Decrypt zone and entering the passkey you provided them with, if it's encrypted. Likewise, you'll be able to decipher whichever posts you possess the password for.", + "paragraph3": "I realise that nobody asked for this, I just made it as a fun way to send secret messages to the people who visit my site alifeinbinary.com and thought it would be a fun project to share as open source for those who are wanting to learn about the technology within." }, - "label": "Download" - }, - "nodesize": { - "label": "Node size" - }, - "password": { - "hint": "Enter a secret key", - "enable": "Enable encryption", - "tooltip": { - "content": "Enable AES 128-bit encryption" - } - }, - "post": { - "posted": "Posted" - }, - "postimagebutton": { - "toast": { - "processing": "Processing image...", - "embedding": "Embedding metadata...", - "size": "Determining the size of the image...", - "presignedpayload": "Getting presigned post payload...", - "uploading": "Uploading to S3 bucket...", - "createimage": "Creating binary feed image...", - "createimagepost": "Creating binary image post...", - "publishing": "Binary image post created.", - "success": "Image uploaded successfully!", - "noencryption": "Encryption disabled. Only encrypted messages can be posted.", - "nocanvasorinput": "No image to upload." + "technology1": "
  • Typescript
  • React
  • HTML Canvas
  • Cryptography in the browser
  • Image generation and consumption
  • ", + "technology2": "
  • Embedding metadata in files
  • CRUD operations to a serverless API on AWS using GraphQL
  • File storage on AWS S3 managed by a cron job
  • Jest tests
  • Internationalisation
  • ", + "review": "Is this like Twitter for robots? Why would anyone make this???", + "byline": "A newly retired, former part-time receptionist and recent grandmother of twins I showed this to on the ferry", + "createdwith": "Created with", + "forkthisproject": "Star this project on ", + "likemywork": "Like my work?", + "hireme": "Hire me", + "encrypt": { + "title": "Create", + "modal": { + "title": "How to use this app", + "step1": "Type your message, click to encrypt if desired, and save/share - only those with the password can access it.", + "caveatstitle": "Caveats", + "step2": "Drop image in here and use the password given to decode the message.", + "caveatstext": "As a security measure, some operating systems, notably iOS, strip metadata from images to prevent malicious code from executing on your phone. This means that images you pass along to a third party won't be able to be decrypted if they have been sent from your iPhone.", + "securitytitle": "Security disclaimer", + "securitytext": "As a security measure, some operating systems, notably iOS, strip metadata from images to prevent malicious code from executing on your phone. This means that images you pass along to a third party won't be able to be decrypted if they have been sent from your iPhone." + }, + "placeholder": "Type your thoughts" + }, + "decrypt": { + "title": "Decode", + "description": "

    Click to upload or drag and drop to extract message

    PNG format only

    " + }, + "downloadimagebutton": { + "tooltip": { + "title": "Download image", + "hint": "Enter a message before saving to disk" + }, + "label": "Download" + }, + "nodesize": { + "label": "Node size" + }, + "password": { + "hint": "Enter a secret key", + "enable": "Enable encryption", + "tooltip": { + "content": "Enable AES 128-bit encryption" + } }, - "tooltip": { - "posttofeed": "Post image to the feed", - "entermessage": "Please enter a message and password before posting" + "post": { + "posted": "Posted" }, - "label": "Post" - }, - "feed": { - "title": "Public steggs", - "loadmore": "Load more" - }, - "footer": { - "rights": "All rights reserved", - "license": "Released under the" - }, - "instructions": "Instructions", - "how-it-works": "How it works" -} \ No newline at end of file + "postimagebutton": { + "toast": { + "processing": "Processing image...", + "embedding": "Embedding metadata...", + "size": "Determining the size of the image...", + "presignedpayload": "Getting presigned post payload...", + "uploading": "Uploading to S3 bucket...", + "createimage": "Creating binary feed image...", + "createimagepost": "Creating binary image post...", + "publishing": "Binary image post created.", + "success": "Image uploaded successfully!", + "noencryption": "Encryption disabled. Only encrypted messages can be posted.", + "nocanvasorinput": "No image to upload." + }, + "tooltip": { + "posttofeed": "Post image to the feed", + "entermessage": "Please enter a message and password before posting" + }, + "label": "Post" + }, + "feed": { + "title": "Public steggs", + "loadmore": "Load more" + }, + "footer": { + "rights": "All rights reserved", + "license": "Released under the" + }, + "instructions": "Instructions", + "how-it-works": "How it works", + "make-something": "make something", + "acknowledgements": { + "title": "Acknowledgements", + "paragraph1": "This app would not have been possible without the following open source projects and libraries:" + } +} diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index 1b688b9..088f56e 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -1,90 +1,95 @@ { - "menu": { - "home": "Inicio", - "about": "Acerca de esta app", - "faq": "Preguntas frecuentes" - }, - "title": "stegg", - "description": { - "intro": "Esta es una aplicación full-stack de React escrita en Typescript.", - "howitworks": "Cómo funciona:", - "paragraph1": "Esta aplicación toma el texto ingresado en el campo Cifrar y lo escribe en los metadatos de un archivo PNG recién creado, generado a partir del Canvas. La imagen del canvas representa el mensaje como datos binarios: los nodos rellenos representan 1's y los nodos delineados representan 0's. Si alguien tuviera suficiente paciencia, podría traducir el mensaje de nuevo a texto simplemente interpretando la imagen como valores unicode. Es por eso que hay un botón que determina si el mensaje está incrustado con cifrado AES de 128 bits o como texto plano.", - "paragraph2": "Una vez que hagas clic en Descargar o Publicar, el archivo se descargará en tu dispositivo o se publicará en el Feed, respectivamente. Cualquiera con quien compartas la imagen podrá extraer y descifrar el texto arrastrando el archivo PNG a la zona Descifrar e ingresando la clave que les diste, si está cifrado. Del mismo modo, podrás descifrar las publicaciones para las cuales poseas la contraseña.", - "paragraph3": "Me doy cuenta de que nadie pidió esto, solo lo hice como una forma divertida de enviar mensajes secretos a las personas que visitan mi sitio alifeinbinary.com y pensé que sería un proyecto divertido para compartir como código abierto para aquellos que quieran aprender sobre la tecnología utilizada." - }, - "technology1": "
  • Typescript
  • React
  • Canvas HTML
  • Criptografía en el navegador
  • Generación y consumo de imágenes
  • ", - "technology2": "
  • Incorporación de metadatos en archivos
  • Operaciones CRUD hacia una API sin servidor en AWS usando GraphQL
  • Almacenamiento de archivos en AWS S3 gestionado por un trabajo cron
  • Pruebas con Jest
  • Internationalización
  • ", - "review": "¿Es esto como Twitter para robots? ¿Por qué alguien haría esto???", - "byline": "Una recién jubilada, ex recepcionista a tiempo parcial y reciente abuela de gemelos a quienes les mostré esto en el ferry", - "createdwith": "Creado con", - "forkthisproject": "Dale una estrella al proyecto en ", - "likemywork": "¿Te gusta mi trabajo?", - "hireme": "Contrátame", - "encrypt": { - "title": "Crear", - "modal": { - "title": "Cómo usar esta aplicación", - "step1": "Escribe tu mensaje, haz clic para cifrar si lo deseas, y guarda/compártelo: solo aquellos con la contraseña podrán acceder.", - "caveatstitle": "Advertencias", - "step2": "Suelta la imagen aquí y usa la contraseña proporcionada para decodificar el mensaje.", - "caveatstext": "Como medida de seguridad, algunos sistemas operativos, especialmente iOS, eliminan los metadatos de las imágenes para evitar que el código malicioso se ejecute en tu teléfono. Esto significa que las imágenes que pases a un tercero no podrán ser descifradas si han sido enviadas desde tu iPhone.", - "securitytitle": "Descargo de responsabilidad de seguridad", - "securitytext": "Como medida de seguridad, algunos sistemas operativos, especialmente iOS, eliminan los metadatos de las imágenes para evitar que el código malicioso se ejecute en tu teléfono. Esto significa que las imágenes que pases a un tercero no podrán ser descifradas si han sido enviadas desde tu iPhone." + "menu": { + "home": "Inicio", + "about": "Acerca de esta app", + "faq": "Preguntas frecuentes" }, - "placeholder": "Escribe tus pensamientos" - }, - "decrypt": { - "title": "Descifrar", - "description": "

    Haz clic para subir o arrastra y suelta para extraer el mensaje

    Solo formato PNG

    " - }, - "downloadimagebutton": { - "tooltip": { - "title": "Descargar imagen", - "hint": "Ingresa un mensaje antes de guardar en el disco" + "title": "stegg", + "description": { + "intro": "Esta es una aplicación full-stack de React escrita en Typescript.", + "howitworks": "Cómo funciona:", + "paragraph1": "Esta aplicación toma el texto ingresado en el campo Cifrar y lo escribe en los metadatos de un archivo PNG recién creado, generado a partir del Canvas. La imagen del canvas representa el mensaje como datos binarios: los nodos rellenos representan 1's y los nodos delineados representan 0's. Si alguien tuviera suficiente paciencia, podría traducir el mensaje de nuevo a texto simplemente interpretando la imagen como valores unicode. Es por eso que hay un botón que determina si el mensaje está incrustado con cifrado AES de 128 bits o como texto plano.", + "paragraph2": "Una vez que hagas clic en Descargar o Publicar, el archivo se descargará en tu dispositivo o se publicará en el Feed, respectivamente. Cualquiera con quien compartas la imagen podrá extraer y descifrar el texto arrastrando el archivo PNG a la zona Descifrar e ingresando la clave que les diste, si está cifrado. Del mismo modo, podrás descifrar las publicaciones para las cuales poseas la contraseña.", + "paragraph3": "Me doy cuenta de que nadie pidió esto, solo lo hice como una forma divertida de enviar mensajes secretos a las personas que visitan mi sitio alifeinbinary.com y pensé que sería un proyecto divertido para compartir como código abierto para aquellos que quieran aprender sobre la tecnología utilizada." }, - "label": "Descargar" - }, - "nodesize": { - "label": "Tamaño del nodo" - }, - "password": { - "hint": "Introduce una clave secreta", - "enable": "Habilitar cifrado", - "tooltip": { - "content": "Habilitar cifrado AES de 128 bits" - } - }, - "post": { - "posted": "Publicado" - }, - "postimagebutton": { - "toast": { - "processing": "Procesando imagen...", - "embedding": "Incorporando metadatos...", - "size": "Determinando el tamaño de la imagen...", - "presignedpayload": "Obteniendo la carga útil del post firmado...", - "uploading": "Subiendo al bucket S3...", - "createimage": "Creando imagen binaria del feed...", - "createimagepost": "Creando post de imagen binaria...", - "publishing": "Post de imagen binaria creado.", - "success": "¡Imagen subida con éxito!", - "noencryption": "Cifrado deshabilitado. Solo los mensajes cifrados pueden ser publicados.", - "nocanvasorinput": "No hay imagen para subir." + "technology1": "
  • Typescript
  • React
  • Canvas HTML
  • Criptografía en el navegador
  • Generación y consumo de imágenes
  • ", + "technology2": "
  • Incorporación de metadatos en archivos
  • Operaciones CRUD hacia una API sin servidor en AWS usando GraphQL
  • Almacenamiento de archivos en AWS S3 gestionado por un trabajo cron
  • Pruebas con Jest
  • Internationalización
  • ", + "review": "¿Es esto como Twitter para robots? ¿Por qué alguien haría esto???", + "byline": "Una recién jubilada, ex recepcionista a tiempo parcial y reciente abuela de gemelos a quienes les mostré esto en el ferry", + "createdwith": "Creado con", + "forkthisproject": "Dale una estrella al proyecto en ", + "likemywork": "¿Te gusta mi trabajo?", + "hireme": "Contrátame", + "encrypt": { + "title": "Crear", + "modal": { + "title": "Cómo usar esta aplicación", + "step1": "Escribe tu mensaje, haz clic para cifrar si lo deseas, y guarda/compártelo: solo aquellos con la contraseña podrán acceder.", + "caveatstitle": "Advertencias", + "step2": "Suelta la imagen aquí y usa la contraseña proporcionada para decodificar el mensaje.", + "caveatstext": "Como medida de seguridad, algunos sistemas operativos, especialmente iOS, eliminan los metadatos de las imágenes para evitar que el código malicioso se ejecute en tu teléfono. Esto significa que las imágenes que pases a un tercero no podrán ser descifradas si han sido enviadas desde tu iPhone.", + "securitytitle": "Descargo de responsabilidad de seguridad", + "securitytext": "Como medida de seguridad, algunos sistemas operativos, especialmente iOS, eliminan los metadatos de las imágenes para evitar que el código malicioso se ejecute en tu teléfono. Esto significa que las imágenes que pases a un tercero no podrán ser descifradas si han sido enviadas desde tu iPhone." + }, + "placeholder": "Escribe tus pensamientos" + }, + "decrypt": { + "title": "Descifrar", + "description": "

    Haz clic para subir o arrastra y suelta para extraer el mensaje

    Solo formato PNG

    " + }, + "downloadimagebutton": { + "tooltip": { + "title": "Descargar imagen", + "hint": "Ingresa un mensaje antes de guardar en el disco" + }, + "label": "Descargar" + }, + "nodesize": { + "label": "Tamaño del nodo" + }, + "password": { + "hint": "Introduce una clave secreta", + "enable": "Habilitar cifrado", + "tooltip": { + "content": "Habilitar cifrado AES de 128 bits" + } }, - "tooltip": { - "posttofeed": "Publicar imagen en el feed", - "entermessage": "Por favor, ingresa un mensaje y una contraseña antes de publicar" + "post": { + "posted": "Publicado" }, - "label": "Publicar" - }, - "feed": { - "title": "Stegg públicos", - "loadmore": "Cargar más" - }, - "footer": { - "rights": "Todos los derechos reservados", - "license": "Publicado bajo la" - }, - "instructions": "Instrucciones", - "how-it-works": "Cómo functiona" -} \ No newline at end of file + "postimagebutton": { + "toast": { + "processing": "Procesando imagen...", + "embedding": "Incorporando metadatos...", + "size": "Determinando el tamaño de la imagen...", + "presignedpayload": "Obteniendo la carga útil del post firmado...", + "uploading": "Subiendo al bucket S3...", + "createimage": "Creando imagen binaria del feed...", + "createimagepost": "Creando post de imagen binaria...", + "publishing": "Post de imagen binaria creado.", + "success": "¡Imagen subida con éxito!", + "noencryption": "Cifrado deshabilitado. Solo los mensajes cifrados pueden ser publicados.", + "nocanvasorinput": "No hay imagen para subir." + }, + "tooltip": { + "posttofeed": "Publicar imagen en el feed", + "entermessage": "Por favor, ingresa un mensaje y una contraseña antes de publicar" + }, + "label": "Publicar" + }, + "feed": { + "title": "Stegg públicos", + "loadmore": "Cargar más" + }, + "footer": { + "rights": "Todos los derechos reservados", + "license": "Publicado bajo la" + }, + "instructions": "Instrucciones", + "how-it-works": "Cómo functiona", + "make-something": "crear algo", + "acknowledgements": { + "title": "Agradecimientos", + "paragraph1": "Esta aplicación no habría sido posible sin las siguientes bibliotecas y proyectos de código abierto:" + } +} diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index d8c3d80..8fd01eb 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -1,90 +1,95 @@ { - "menu": { - "home": "Accueil", - "about": "À propos de cette app", - "faq": "FAQ" - }, - "title": "stegg", - "description": { - "intro": "Ceci est une application full-stack React écrite en Typescript.", - "howitworks": "Comment ça fonctionne :", - "paragraph1": "Cette application prend le texte saisi dans le champ Chiffrer et l'écrit dans les métadonnées d'un fichier PNG nouvellement créé, généré à partir de la Canvas. L'image du canvas représente le message sous forme de données binaires : les nœuds remplis représentent des 1 et les nœuds délimités représentent des 0. Si quelqu'un était assez patient, il pourrait traduire le message en texte simplement en interprétant l'image comme des valeurs unicode. C'est pourquoi il y a un bouton qui détermine si le message est intégré avec un cryptage AES 128 bits ou en tant que texte brut.", - "paragraph2": "Une fois que vous cliquez sur Télécharger ou Poster, le fichier sera téléchargé sur votre appareil ou posté dans le Feed, respectivement. Toute personne avec qui vous partagez l'image pourra extraire et déchiffrer le texte en glissant le fichier PNG dans la zone Déchiffrer et en entrant la clé que vous leur avez fournie, si c'est crypté. De la même manière, vous pourrez déchiffrer tous les messages pour lesquels vous avez le mot de passe.", - "paragraph3": "Je me rends compte que personne n'a demandé cela, je l'ai juste fait comme un moyen amusant d'envoyer des messages secrets aux gens qui visitent mon site alifeinbinary.com et j'ai pensé que ce serait un projet amusant à partager en open source pour ceux qui souhaitent en apprendre davantage sur la technologie utilisée." - }, - "technology1": "
  • Typescript
  • React
  • Canvas HTML
  • Cryptographie dans le navigateur
  • Génération et consommation d'images
  • ", - "technology2": "
  • Incorporation de métadonnées dans les fichiers
  • Opérations CRUD vers une API sans serveur sur AWS à l'aide de GraphQL
  • Stockage de fichiers sur AWS S3 géré par une tâche cron
  • Tests Jest
  • Internationalisation
  • ", - "review": "Est-ce comme Twitter pour robots ? Pourquoi quelqu'un ferait-il cela ???", - "byline": "Une nouvelle retraitée, ancienne réceptionniste à temps partiel et grand-mère récente de jumeaux à qui j'ai montré cela sur le ferry", - "createdwith": "Créé avec", - "forkthisproject": "Donnez une étoile au projet à ", - "likemywork": "Aimez mon travail ?", - "hireme": "Engagez-moi", - "encrypt": { - "title": "Créer", - "modal": { - "title": "Comment utiliser cette application", - "step1": "Tapez votre message, cliquez pour chiffrer si souhaité, puis sauvegardez/partagez - seuls ceux avec le mot de passe pourront y accéder.", - "caveatstitle": "Mises en garde", - "step2": "Déposez l'image ici et utilisez le mot de passe fourni pour décoder le message.", - "caveatstext": "Par mesure de sécurité, certains systèmes d'exploitation, notamment iOS, suppriment les métadonnées des images pour empêcher l'exécution de code malveillant sur votre téléphone. Cela signifie que les images que vous transférez à une tierce partie ne pourront pas être déchiffrées si elles ont été envoyées depuis votre iPhone.", - "securitytitle": "Avertissement de sécurité", - "securitytext": "Par mesure de sécurité, certains systèmes d'exploitation, notamment iOS, suppriment les métadonnées des images pour empêcher l'exécution de code malveillant sur votre téléphone. Cela signifie que les images que vous transférez à une tierce partie ne pourront pas être déchiffrées si elles ont été envoyées depuis votre iPhone." + "menu": { + "home": "Accueil", + "about": "À propos de cette app", + "faq": "FAQ" }, - "placeholder": "Tapez vos pensées" - }, - "decrypt": { - "title": "Déchiffrer", - "description": "

    Cliquez pour télécharger ou glissez-déposez pour extraire le message

    Format PNG uniquement

    " - }, - "downloadimagebutton": { - "tooltip": { - "title": "Télécharger l'image", - "hint": "Entrez un message avant de sauvegarder sur le disque" + "title": "stegg", + "description": { + "intro": "Ceci est une application full-stack React écrite en Typescript.", + "howitworks": "Comment ça fonctionne :", + "paragraph1": "Cette application prend le texte saisi dans le champ Chiffrer et l'écrit dans les métadonnées d'un fichier PNG nouvellement créé, généré à partir de la Canvas. L'image du canvas représente le message sous forme de données binaires : les nœuds remplis représentent des 1 et les nœuds délimités représentent des 0. Si quelqu'un était assez patient, il pourrait traduire le message en texte simplement en interprétant l'image comme des valeurs unicode. C'est pourquoi il y a un bouton qui détermine si le message est intégré avec un cryptage AES 128 bits ou en tant que texte brut.", + "paragraph2": "Une fois que vous cliquez sur Télécharger ou Poster, le fichier sera téléchargé sur votre appareil ou posté dans le Feed, respectivement. Toute personne avec qui vous partagez l'image pourra extraire et déchiffrer le texte en glissant le fichier PNG dans la zone Déchiffrer et en entrant la clé que vous leur avez fournie, si c'est crypté. De la même manière, vous pourrez déchiffrer tous les messages pour lesquels vous avez le mot de passe.", + "paragraph3": "Je me rends compte que personne n'a demandé cela, je l'ai juste fait comme un moyen amusant d'envoyer des messages secrets aux gens qui visitent mon site alifeinbinary.com et j'ai pensé que ce serait un projet amusant à partager en open source pour ceux qui souhaitent en apprendre davantage sur la technologie utilisée." }, - "label": "Télécharger" - }, - "nodesize": { - "label": "Taille du nœud" - }, - "password": { - "hint": "Entrez une clé secrète", - "enable": "Activer le chiffrement", - "tooltip": { - "content": "Activer le chiffrement AES 128 bits" - } - }, - "post": { - "posted": "Posté" - }, - "postimagebutton": { - "toast": { - "processing": "Traitement de l'image...", - "embedding": "Incorporation des métadonnées...", - "size": "Détermination de la taille de l'image...", - "presignedpayload": "Récupération de la charge utile de post signé...", - "uploading": "Téléchargement dans le bucket S3...", - "createimage": "Création de l'image binaire du feed...", - "createimagepost": "Création du post d'image binaire...", - "publishing": "Post d'image binaire créé.", - "success": "Image téléchargée avec succès !", - "noencryption": "Chiffrement désactivé. Seuls les messages chiffrés peuvent être postés.", - "nocanvasorinput": "Aucune image à télécharger." + "technology1": "
  • Typescript
  • React
  • Canvas HTML
  • Cryptographie dans le navigateur
  • Génération et consommation d'images
  • ", + "technology2": "
  • Incorporation de métadonnées dans les fichiers
  • Opérations CRUD vers une API sans serveur sur AWS à l'aide de GraphQL
  • Stockage de fichiers sur AWS S3 géré par une tâche cron
  • Tests Jest
  • Internationalisation
  • ", + "review": "Est-ce comme Twitter pour robots ? Pourquoi quelqu'un ferait-il cela ???", + "byline": "Une nouvelle retraitée, ancienne réceptionniste à temps partiel et grand-mère récente de jumeaux à qui j'ai montré cela sur le ferry", + "createdwith": "Créé avec", + "forkthisproject": "Donnez une étoile au projet à ", + "likemywork": "Aimez mon travail ?", + "hireme": "Engagez-moi", + "encrypt": { + "title": "Créer", + "modal": { + "title": "Comment utiliser cette application", + "step1": "Tapez votre message, cliquez pour chiffrer si souhaité, puis sauvegardez/partagez - seuls ceux avec le mot de passe pourront y accéder.", + "caveatstitle": "Mises en garde", + "step2": "Déposez l'image ici et utilisez le mot de passe fourni pour décoder le message.", + "caveatstext": "Par mesure de sécurité, certains systèmes d'exploitation, notamment iOS, suppriment les métadonnées des images pour empêcher l'exécution de code malveillant sur votre téléphone. Cela signifie que les images que vous transférez à une tierce partie ne pourront pas être déchiffrées si elles ont été envoyées depuis votre iPhone.", + "securitytitle": "Avertissement de sécurité", + "securitytext": "Par mesure de sécurité, certains systèmes d'exploitation, notamment iOS, suppriment les métadonnées des images pour empêcher l'exécution de code malveillant sur votre téléphone. Cela signifie que les images que vous transférez à une tierce partie ne pourront pas être déchiffrées si elles ont été envoyées depuis votre iPhone." + }, + "placeholder": "Tapez vos pensées" + }, + "decrypt": { + "title": "Déchiffrer", + "description": "

    Cliquez pour télécharger ou glissez-déposez pour extraire le message

    Format PNG uniquement

    " + }, + "downloadimagebutton": { + "tooltip": { + "title": "Télécharger l'image", + "hint": "Entrez un message avant de sauvegarder sur le disque" + }, + "label": "Télécharger" + }, + "nodesize": { + "label": "Taille du nœud" + }, + "password": { + "hint": "Entrez une clé secrète", + "enable": "Activer le chiffrement", + "tooltip": { + "content": "Activer le chiffrement AES 128 bits" + } }, - "tooltip": { - "posttofeed": "Poster l'image sur le feed", - "entermessage": "Veuillez entrer un message et un mot de passe avant de poster" + "post": { + "posted": "Posté" }, - "label": "Poster" - }, - "feed": { - "title": "Stegg publiques ", - "loadmore": "Charger plus" - }, - "footer": { - "rights": "Tous droits réservés", - "license": "Publié sous la" - }, - "instructions": "Instructions", - "how-it-works": "Comment ça marche" -} \ No newline at end of file + "postimagebutton": { + "toast": { + "processing": "Traitement de l'image...", + "embedding": "Incorporation des métadonnées...", + "size": "Détermination de la taille de l'image...", + "presignedpayload": "Récupération de la charge utile de post signé...", + "uploading": "Téléchargement dans le bucket S3...", + "createimage": "Création de l'image binaire du feed...", + "createimagepost": "Création du post d'image binaire...", + "publishing": "Post d'image binaire créé.", + "success": "Image téléchargée avec succès !", + "noencryption": "Chiffrement désactivé. Seuls les messages chiffrés peuvent être postés.", + "nocanvasorinput": "Aucune image à télécharger." + }, + "tooltip": { + "posttofeed": "Poster l'image sur le feed", + "entermessage": "Veuillez entrer un message et un mot de passe avant de poster" + }, + "label": "Poster" + }, + "feed": { + "title": "Stegg publiques ", + "loadmore": "Charger plus" + }, + "footer": { + "rights": "Tous droits réservés", + "license": "Publié sous la" + }, + "instructions": "Instructions", + "how-it-works": "Comment ça marche", + "make-something": "créer quelque chose", + "acknowledgements": { + "title": "Remerciements", + "paragraph1": "Cette application n'aurait pas été possible sans les projets et bibliothèques open source suivants:" + } +} diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx index 3ff8700..1e49fdf 100644 --- a/src/components/Menu.tsx +++ b/src/components/Menu.tsx @@ -141,12 +141,12 @@ const Menu: React.FC = () => { g g -

    make something

    +

    {t('make-something')}

    - - + + {t('menu.about')} @@ -157,7 +157,7 @@ const Menu: React.FC = () => { changeLanguage('fr')}>French 🇫🇷 - + diff --git a/src/index.css b/src/index.css index 7ed058d..ae7725d 100644 --- a/src/index.css +++ b/src/index.css @@ -1,75 +1,78 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - --toastify-color-light: #fff; - --toastify-color-dark: #1D293B; - --toastify-color-info: #aaddcc; - --toastify-color-success: #aabb99; - --toastify-color-warning: #ff8866; - --toastify-color-error: #ccaabb; - --toastify-color-transparent: rgba(255, 255, 255, 0.7); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + --toastify-color-light: #fff; + --toastify-color-dark: #1d293b; + --toastify-color-info: #aaddcc; + --toastify-color-success: #aabb99; + --toastify-color-warning: #ff8866; + --toastify-color-error: #ccaabb; + --toastify-color-transparent: rgba(255, 255, 255, 0.7); - --toastify-icon-color-info: var(--toastify-color-info); - --toastify-icon-color-success: var(--toastify-color-success); - --toastify-icon-color-warning: var(--toastify-color-warning); - --toastify-icon-color-error: var(--toastify-color-error); - --toastify-toast-border-radius: 10px; - --toastify-toast-border-width: 1px; - --toastify-toast-border-color: #fff; + --toastify-icon-color-info: var(--toastify-color-info); + --toastify-icon-color-success: var(--toastify-color-success); + --toastify-icon-color-warning: var(--toastify-color-warning); + --toastify-icon-color-error: var(--toastify-color-error); + --toastify-toast-border-radius: 10px; + --toastify-toast-border-width: 1px; + --toastify-toast-border-color: #fff; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; } @layer components { - input[type=range] { - @apply appearance-none bg-transparent; - } - - input[type=range]::-webkit-slider-runnable-track { - @apply bg-seablue/25 rounded-full; - } + input[type="range"] { + @apply appearance-none bg-transparent; + } - input[type=range]::-moz-range-track { - @apply bg-seablue/25 rounded-full; - } + input[type="range"]::-webkit-slider-runnable-track { + @apply rounded-full bg-seablue/25; + } - input[type=range]::-ms-track { - @apply bg-seablue/25 rounded-full; - } + input[type="range"]::-moz-range-track { + @apply rounded-full bg-seablue/25; + } - input[type="range"]::-moz-range-progress { - @apply bg-seablue/25 rounded-full; - } + input[type="range"]::-ms-track { + @apply rounded-full bg-seablue/25; + } - input[type="range"]::-moz-range-thumb { - @apply w-4 h-4 bg-seablue rounded-full; - } + input[type="range"]::-moz-range-progress { + @apply rounded-full bg-seablue/25; + } - input[type="range"]::-webkit-slider-thumb { - @apply w-4 h-4 bg-seablue rounded-full; - } + input[type="range"]::-moz-range-thumb { + @apply h-4 w-4 rounded-full bg-seablue; + } + input[type="range"]::-webkit-slider-thumb { + @apply h-4 w-4 rounded-full bg-seablue; + } } .Toastify__toast { - border: 2px solid #99aabb; + border: 2px solid #99aabb; } -@tailwind base; -@tailwind components; -@tailwind utilities; \ No newline at end of file +#content-wrapper { + overflow: hidden; +} diff --git a/src/main.tsx b/src/main.tsx index 3e2213d..b1f843f 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -74,8 +74,6 @@ const client = new ApolloClient({ cache }); -const publicUrl = import.meta.env.VITE_PUBLIC_URL; -console.log("publicUrl", publicUrl); createRoot(document.getElementById('root')!).render( diff --git a/src/routes/About.tsx b/src/routes/About.tsx index c093d3d..4255f3a 100644 --- a/src/routes/About.tsx +++ b/src/routes/About.tsx @@ -15,29 +15,90 @@ * along with this program. If not, see . */ +import { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { json, Link, LoaderFunction, Outlet } from 'react-router-dom'; +import { json, Link, LoaderFunction, Outlet, useLocation } from 'react-router-dom'; import { Navbar } from 'flowbite-react/components/Navbar'; -import { customNavbarTheme } from '../utils/customTheme'; +import { CustomFlowbiteTheme } from 'flowbite-react/components/Flowbite'; +import { motion, AnimatePresence } from 'framer-motion'; +const customNavbarTheme: CustomFlowbiteTheme["navbar"] = { + root: { + base: "w-full bg-white dark:bg-slate-900 px-0 pb-2.5 items-end justify-end", + inner: { + base: "mx-auto flex flex-wrap justify-end", + }, + }, +}; const About: React.FC = () => { const { t } = useTranslation(); + const location = useLocation(); + const [contentHeight, setContentHeight] = useState('auto'); + const [isHeightTransitionDone, setIsHeightTransitionDone] = useState(false); + const [isContentVisible, setIsContentVisible] = useState(true); + const contentWrapperRef = useRef(null); + + useEffect(() => { + const contentElement = contentWrapperRef.current; + if (contentElement) { + // Reset state to hide content and prepare for height animation + setIsContentVisible(false); + setIsHeightTransitionDone(false); + + // Measure new content height without showing it + const height = contentElement.scrollHeight; + setContentHeight(`${height}px`); + + // Once height animation completes, show content + setTimeout(() => { + setIsHeightTransitionDone(true); // Height animation done, now show content + setIsContentVisible(true); // Trigger the fade-in + }); // Match this duration to the height animation timing + } + }, [location.pathname]); + + const spring = { + type: "spring", + damping: 10, + stiffness: 50 + } + return (
    - - - - {t('instructions')} - + + + {t('instructions')} - - - {t('how-it-works')} - + + {t('how-it-works')} - +
    + + + + + +
    ) } diff --git a/src/routes/HowItWorks.tsx b/src/routes/HowItWorks.tsx index e89f8a9..a7b5370 100644 --- a/src/routes/HowItWorks.tsx +++ b/src/routes/HowItWorks.tsx @@ -15,17 +15,47 @@ * along with this program. If not, see . */ +import { useState } from "react"; import { faNodeJs, faNpm, faReact, faAws, faFirefoxBrowser } from "@fortawesome/free-brands-svg-icons"; import { faLock, faAsterisk, faStar, faFontAwesome } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Modal } from "flowbite-react"; import { t } from "i18next"; import { Trans } from "react-i18next"; import { json, LoaderFunction } from "react-router-dom"; const HowItWorks: React.FC = () => { + const [openModal, setOpenModal] = useState(false); + + const dependencies = { + "dependencies": { + "@apollo/client": "^3.11.8", + "@fortawesome/free-brands-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@fortawesome/react-fontawesome": "^0.2.2", + "apollo3-cache-persist": "^0.15.0", + "crypto-js": "^4.2.0", + "file-saver": "^2.0.5", + "flowbite-react": "^0.10.2", + "framer-motion": "^11.11.1", + "i18next": "^23.15.2", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^2.6.2", + "jest-environment-jsdom": "^29.7.0", + "meta-png": "^1.0.6", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-dropzone": "^14.2.9", + "react-i18next": "^15.0.2", + "react-router-dom": "^6.26.2", + "react-toastify": "^10.0.5", + "zustand": "^4.5.5" + }, + } + return (
    -

    {t('description.intro')}

    +

    {t('description.intro')}

    {t('description.howitworks')} , FontAwesomeIcon: }}>This app takes the text input from Encrypt field and writes it to the metadata of a newly created PNG file that is generated from the Canvas. The canvas image depicts the message as binary data: nodes that are filled represent 1's and nodes that are outlined represent 0's. If someone was patient enough, they could translate the message back to text just by interpreting the image as unicode values. That's why there is a toggle that deteremines whether the message is embedded with 128-bit AES encryption or as plain text.

    , }}>Once you click Save or Post, the file will be downloaded to your device or posted to the Feed, respectively. Whomever you share the image with would be able to extract and decipher the text by dragging the PNG file into the Decrypt zone and entering the passkey you provided them with, if it's encrypted. Likewise, you'll be able to decipher whichever posts you possess the password for.

    , a: }}>I realise that nobody asked for this, I just made it as a fun way to send secret messages to the people who visit my site alifeinbinary.com and thought it would be a fun project to share as open source for those who are wanting to learn about the technology within.

    @@ -71,6 +101,7 @@ const HowItWorks: React.FC = () => {
    + setOpenModal(true)}>{t('acknowledgements.title')}

    @@ -112,6 +143,23 @@ const HowItWorks: React.FC = () => {
    + setOpenModal(false)}> + {t('acknowledgements.title')} + +
    +

    + {t('acknowledgements.paragraph1')} +

    +

    +

    +                                
    +                                    {JSON.stringify(dependencies, null, 2)}
    +                                
    +                            
    +

    +
    +
    +
    ); }; diff --git a/src/routes/Instructions.tsx b/src/routes/Instructions.tsx index b10bb26..0529e25 100644 --- a/src/routes/Instructions.tsx +++ b/src/routes/Instructions.tsx @@ -20,7 +20,9 @@ import { json, LoaderFunction } from "react-router-dom"; const Instructions: React.FC = () => { return (
    -

    Instructions

    +

    Instructions

    + +

    Click on the "How it Works" tab to see how to use the image generator.

    ); } diff --git a/src/routes/PostPage.tsx b/src/routes/PostPage.tsx index e21dd6d..69b1879 100644 --- a/src/routes/PostPage.tsx +++ b/src/routes/PostPage.tsx @@ -15,7 +15,8 @@ * along with this program. If not, see . */ -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' +import { AnimatePresence, motion } from 'framer-motion'; import { LoaderFunction, LoaderFunctionArgs, useParams, useLocation } from "react-router-dom"; import { PostProps } from "../types"; import { useGetBinaryImagePost } from "../hooks/useGetBinaryImagePost"; @@ -29,8 +30,34 @@ const PostPage: React.FC = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const location = useLocation(); + const [contentHeight, setContentHeight] = useState('auto'); + const [isHeightTransitionDone, setIsHeightTransitionDone] = useState(false); + const [isContentVisible, setIsContentVisible] = useState(true); + const contentWrapperRef = useRef(null); + + const spring = { + type: "spring", + damping: 10, + stiffness: 50 + } useEffect(() => { + const contentElement = contentWrapperRef.current; + if (contentElement) { + // Reset state to hide content and prepare for height animation + setIsContentVisible(true); + setIsHeightTransitionDone(false); + + // Measure new content height without showing it + const height = contentElement.scrollHeight; + setContentHeight(`${height}px`); + + // Once height animation completes, show content + setTimeout(() => { + setIsHeightTransitionDone(true); // Height animation done, now show content + setIsContentVisible(true); // Trigger the fade-in + }); // Match this duration to the height animation timing + } const fetchData = async () => { try { const result = await getBinaryImagePost(postId); @@ -56,8 +83,33 @@ const PostPage: React.FC = () => { } if (data && data.getBinaryImagePost && data.getBinaryImagePost.data) { const { id, entryId, author, posted, image, width, height, key } = data.getBinaryImagePost.data; - console.log("data", data); - return ; + return ( +
    + + + + + +
    + ); } } diff --git a/src/utils/customTheme.ts b/src/utils/customTheme.ts index 330669a..a9b0b5f 100644 --- a/src/utils/customTheme.ts +++ b/src/utils/customTheme.ts @@ -56,7 +56,7 @@ export const customNavbarTheme: CustomFlowbiteTheme["navbar"] = { base: "w-full bg-white dark:bg-slate-900 px-0 py-2.5", }, link: { - base: "block py-1 px-1 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent", + base: "block py-1 px-1 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent transition duration-500 ease-in-out", active: { on: "bg-blue-700 text-white md:bg-transparent md:text-blue-700", off: "", diff --git a/vite.config.ts b/vite.config.ts index df0150f..6f6429f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,9 +2,9 @@ import react from "@vitejs/plugin-react"; import { visualizer } from "rollup-plugin-visualizer"; import { defineConfig } from "vite"; import viteCompression from "vite-plugin-compression"; +import { ViteMinifyPlugin } from "vite-plugin-minify"; import Sitemap from "vite-plugin-sitemap"; import { purgeCss } from "vite-plugin-tailwind-purgecss"; - // https://vitejs.dev/config/ export default defineConfig({ plugins: [ @@ -23,6 +23,7 @@ export default defineConfig({ filename: "./dist/stats.html", open: true, }), + ViteMinifyPlugin({}), ], base: "/", build: { @@ -39,4 +40,7 @@ export default defineConfig({ minify: "esbuild", cssCodeSplit: true, }, + server: { + hmr: true, + }, });