├── .commitlintrc.json ├── .czrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── build_docker.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.json ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .stylelintignore ├── .stylelintrc.json ├── .versionrc.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── Caddyfile ├── Dockerfile ├── LICENSE ├── README.md ├── astro.config.mjs ├── docker-compose.yml ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public ├── favicon.svg ├── fonts │ ├── bebas-neue-v14-latin-regular.woff2 │ ├── inter-tight-v7-latin-500.woff2 │ ├── inter-tight-v7-latin-600.woff2 │ ├── inter-tight-v7-latin-700.woff2 │ ├── inter-v13-latin-500.woff2 │ ├── inter-v13-latin-regular.woff2 │ ├── space-mono-v13-latin-700.woff2 │ └── space-mono-v13-latin-regular.woff2 ├── images │ └── noise.png ├── logo.svg ├── og.png └── robots.txt ├── src ├── components │ ├── app │ │ ├── app.module.css │ │ ├── app.tsx │ │ └── index.ts │ ├── binary │ │ ├── binary.module.css │ │ ├── binary.tsx │ │ └── index.ts │ ├── checkbox │ │ ├── checkbox.module.css │ │ ├── checkbox.tsx │ │ └── index.ts │ ├── cipher-text.tsx │ ├── container │ │ ├── container.module.css │ │ ├── container.tsx │ │ └── index.ts │ ├── description.astro │ ├── footer.astro │ ├── hero.astro │ ├── matrix.astro │ └── slider │ │ ├── index.ts │ │ ├── slider.module.css │ │ └── slider.tsx ├── data │ └── wordlist.ts ├── env.d.ts ├── helpers │ ├── crypto.ts │ ├── number.ts │ ├── random.ts │ ├── string.ts │ ├── styles.ts │ └── time.ts ├── hooks │ ├── use-copy.ts │ └── use-local-storage.ts ├── layouts │ └── layout.astro ├── pages │ └── index.astro └── styles │ ├── base │ └── base.css │ ├── fonts.css │ ├── global.css │ └── variables │ ├── color.css │ ├── index.css │ └── typography.css └── tsconfig.json /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./node_modules/cz-conventional-changelog" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .output/ 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "env": { 5 | "browser": true, 6 | "amd": true, 7 | "node": true, 8 | "es2022": true 9 | }, 10 | 11 | "parser": "@typescript-eslint/parser", 12 | 13 | "parserOptions": { 14 | "ecmaVersion": "latest", 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true 18 | } 19 | }, 20 | 21 | "extends": [ 22 | "eslint:recommended", 23 | "plugin:@typescript-eslint/recommended", 24 | "plugin:typescript-sort-keys/recommended", 25 | "plugin:import/recommended", 26 | "plugin:react/recommended", 27 | "plugin:react/jsx-runtime", 28 | "plugin:jsx-a11y/recommended", 29 | "plugin:react-hooks/recommended", 30 | "plugin:astro/recommended", 31 | "prettier" 32 | ], 33 | 34 | "plugins": [ 35 | "@typescript-eslint", 36 | "typescript-sort-keys", 37 | "sort-keys-fix", 38 | "sort-destructure-keys", 39 | "prettier" 40 | ], 41 | 42 | "rules": { 43 | "prettier/prettier": "error", 44 | "sort-keys-fix/sort-keys-fix": ["warn", "asc"], 45 | "sort-destructure-keys/sort-destructure-keys": "warn", 46 | "@typescript-eslint/triple-slash-reference": "off", 47 | "jsx-a11y/no-static-element-interactions": "off", 48 | "jsx-a11y/label-has-associated-control": "off", 49 | "react/jsx-sort-props": [ 50 | "warn", 51 | { 52 | "callbacksLast": true, 53 | "multiline": "last" 54 | } 55 | ] 56 | }, 57 | 58 | "settings": { 59 | "react": { 60 | "version": "detect" 61 | }, 62 | 63 | "import/parsers": { 64 | "@typescript-eslint/parser": [".ts", ".tsx", ".js", ".jsx"] 65 | }, 66 | 67 | "import/resolver": { 68 | "typescript": true, 69 | "node": true, 70 | "alias": { 71 | "extensions": [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 72 | "map": [["@", "./src"]] 73 | } 74 | } 75 | }, 76 | 77 | "overrides": [ 78 | { 79 | "files": ["**/*.astro"], 80 | "parser": "astro-eslint-parser", 81 | 82 | "parserOptions": { 83 | "parser": "@typescript-eslint/parser", 84 | "extraFileExtensions": [".astro"] 85 | }, 86 | 87 | "rules": { 88 | "prettier/prettier": "error", 89 | "react/no-unknown-property": "off", 90 | "react/jsx-key": "off" 91 | }, 92 | 93 | "globals": { 94 | "Astro": "readonly" 95 | } 96 | }, 97 | 98 | { 99 | "files": ["**/*.astro/*.js"], 100 | "rules": { 101 | "prettier/prettier": "off" 102 | } 103 | } 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /.github/workflows/build_docker.yml: -------------------------------------------------------------------------------- 1 | name: Build and push main image 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | push-store-image: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: 'Checkout GitHub Action' 14 | uses: actions/checkout@main 15 | 16 | - name: 'Login to GitHub Container Registry' 17 | uses: docker/login-action@v1 18 | with: 19 | registry: ghcr.io 20 | username: ${{github.actor}} 21 | password: ${{secrets.ACCESS_TOKEN}} 22 | 23 | - name: Set up QEMU 24 | uses: docker/setup-qemu-action@v1 25 | 26 | - name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v1 28 | 29 | - name: 'Build and push Inventory Image' 30 | run: | 31 | IMAGE_NAME="ghcr.io/remvze/pswd" 32 | 33 | GIT_TAG=${{ github.ref }} 34 | GIT_TAG=${GIT_TAG#refs/tags/} 35 | 36 | docker buildx build \ 37 | --platform linux/amd64,linux/arm64 \ 38 | -t $IMAGE_NAME:latest \ 39 | -t $IMAGE_NAME:$GIT_TAG \ 40 | --push . 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{ts,tsx,js,jsx}": "eslint --fix", 3 | "*.{json,md}": "prettier --write", 4 | "*.css": "stylelint --fix", 5 | "*.astro": ["eslint --fix", "stylelint --fix"], 6 | "*.html": ["prettier --write", "stylelint --fix"] 7 | } 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .output/ 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-astro"], 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "endOfLine": "lf", 6 | "tabWidth": 2, 7 | "semi": true 8 | } 9 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .output/ 4 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-recess-order", 5 | "stylelint-config-html", 6 | "stylelint-prettier/recommended" 7 | ], 8 | 9 | "rules": { 10 | "import-notation": "string", 11 | "selector-class-pattern": null, 12 | "no-descending-specificity": null 13 | }, 14 | 15 | "overrides": [ 16 | { 17 | "files": ["*.astro"], 18 | "rules": { 19 | "prettier/prettier": null 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { 4 | "type": "feat", 5 | "section": "✨ Features" 6 | }, 7 | { 8 | "type": "fix", 9 | "section": "🐛 Bug Fixes" 10 | }, 11 | { 12 | "type": "chore", 13 | "hidden": false, 14 | "section": "🚚 Chores" 15 | }, 16 | { 17 | "type": "docs", 18 | "hidden": false, 19 | "section": "📝 Documentation" 20 | }, 21 | { 22 | "type": "style", 23 | "hidden": false, 24 | "section": "💄 Styling" 25 | }, 26 | { 27 | "type": "refactor", 28 | "hidden": false, 29 | "section": "♻️ Code Refactoring" 30 | }, 31 | { 32 | "type": "perf", 33 | "hidden": false, 34 | "section": "⚡️ Performance Improvements" 35 | }, 36 | { 37 | "type": "test", 38 | "hidden": false, 39 | "section": "✅ Testing" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "astro-build.astro-vscode", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "stylelint.vscode-stylelint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n", 3 | "eslint.validate": [ 4 | "javascript", 5 | "javascriptreact", 6 | "typescript", 7 | "typescriptreact", 8 | "astro" 9 | ], 10 | "stylelint.validate": ["css", "html", "astro"], 11 | "editor.defaultFormatter": "esbenp.prettier-vscode", 12 | "editor.formatOnSave": true, 13 | "editor.codeActionsOnSave": { 14 | "source.fixAll.eslint": "explicit", 15 | "source.fixAll.stylelint": "explicit" 16 | }, 17 | "[javascript][javascriptreact][typescript][typescriptreact][astro]": { 18 | "editor.formatOnSave": false 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.0.1](https://github.com/remvze/pswd/compare/v1.0.0...v1.0.1) (2025-03-17) 6 | 7 | 8 | ### 💄 Styling 9 | 10 | * add shines ([16706d8](https://github.com/remvze/pswd/commit/16706d8dcae877707f8895b985941849f50afd25)) 11 | * minor change ([42a585f](https://github.com/remvze/pswd/commit/42a585f728180d069f48f87596ba6429bfeeeae3)) 12 | * minor change ([08fd4df](https://github.com/remvze/pswd/commit/08fd4df9e8c856cd949391c691e0178c27246be4)) 13 | * minor changes ([a541106](https://github.com/remvze/pswd/commit/a5411066f1232a66cbff70e51a927cd45744632d)) 14 | * minor changes ([7539370](https://github.com/remvze/pswd/commit/7539370eb0806166e01be31837cf6629b6bc1d95)) 15 | 16 | 17 | ### ✨ Features 18 | 19 | * implement custom checkbox ([db2d60a](https://github.com/remvze/pswd/commit/db2d60ae536abd006bafdc38cbba03bee0c37cd7)) 20 | * implement custom slider ([acf5453](https://github.com/remvze/pswd/commit/acf545341eafc0585e058013ccb05eea74bd6b9d)) 21 | * improve the matrix ([761bd0b](https://github.com/remvze/pswd/commit/761bd0b0102ba48aafeacbdddff9f5387f24016f)) 22 | * make cipher text repeatable ([cbd6344](https://github.com/remvze/pswd/commit/cbd6344ae0ee49f334e16dedbbe9058b155819f7)) 23 | 24 | 25 | ### 🐛 Bug Fixes 26 | 27 | * escape special characters ([7eee6d9](https://github.com/remvze/pswd/commit/7eee6d99cc53c5a0e67d98864ea42d0d3360a346)) 28 | 29 | ## [1.0.0](https://github.com/remvze/pswd/compare/v0.0.8...v1.0.0) (2025-02-12) 30 | 31 | 32 | ### 🐛 Bug Fixes 33 | 34 | * add variable ([92cf681](https://github.com/remvze/pswd/commit/92cf6815e7cdce3d5aeb6a2314a2bdc0322b266c)) 35 | * do not reset on height change ([a791de5](https://github.com/remvze/pswd/commit/a791de510130d9d736ef3de854db477f6aba1d63)) 36 | 37 | 38 | ### ✨ Features 39 | 40 | * add matrix effect ([f7bf25d](https://github.com/remvze/pswd/commit/f7bf25dd9d949def72caff15d9341154edf4bed6)) 41 | * add new hero ([25de17f](https://github.com/remvze/pswd/commit/25de17f057bdae051034a178b79f21021b37cb0c)) 42 | * add new hero ([66fcee2](https://github.com/remvze/pswd/commit/66fcee2e5c56c438cab56f371589d652a5d8737e)) 43 | * add pin tab ([8398562](https://github.com/remvze/pswd/commit/83985624f33c754186bb97ce9b42f3fae3094740)) 44 | * better hero ([613fd05](https://github.com/remvze/pswd/commit/613fd053a00ebb6ffc4d2623f6171d1c19c42e2b)) 45 | * improve hero ([a3f56da](https://github.com/remvze/pswd/commit/a3f56da7caba4b361147cd269fbf10e532ceae3e)) 46 | * make the effect responsive ([6014e7e](https://github.com/remvze/pswd/commit/6014e7e6776c2bcb299e9a2e1c2f6edcde300e46)) 47 | * redesign ([f6c52f5](https://github.com/remvze/pswd/commit/f6c52f59a7ce72a306b324441746349ff6ce4970)) 48 | * remove scores for pin ([261ce22](https://github.com/remvze/pswd/commit/261ce22a250a2e420c73463f04d583e319be5d2b)) 49 | 50 | 51 | ### 💄 Styling 52 | 53 | * add shades ([f3ee3d4](https://github.com/remvze/pswd/commit/f3ee3d43db1840d69aa2619ff2896e98160f9262)) 54 | * change color ([009a6ab](https://github.com/remvze/pswd/commit/009a6abc80b8a40082ac68ea3e87ba8a34c6dd60)) 55 | * change color ([e64a2d8](https://github.com/remvze/pswd/commit/e64a2d832f966be3fb55f193026ab533003629a0)) 56 | * change color ([09e41f0](https://github.com/remvze/pswd/commit/09e41f0dd98226774abb8c0d168cd7b713e2380d)) 57 | * change color ([f4ccd5b](https://github.com/remvze/pswd/commit/f4ccd5b1f322be799c7d59aa618ae8a815b941bf)) 58 | * change color ([ae3efe9](https://github.com/remvze/pswd/commit/ae3efe9a25e077e39698d745eccd4d2e6f861385)) 59 | * change color ([be31543](https://github.com/remvze/pswd/commit/be31543e0fb98e21348bd7eb060bc69b437860ad)) 60 | * change color ([2672a7b](https://github.com/remvze/pswd/commit/2672a7b6d77cb9180732fefe49f9c617f6e62efa)) 61 | * change foreground colors ([ebdb153](https://github.com/remvze/pswd/commit/ebdb1532c9424058a4f9c537db8c8e0286737bca)) 62 | * decrease border radius ([c2b018d](https://github.com/remvze/pswd/commit/c2b018d8099d9dbe44c82f003a245c8c2475764a)) 63 | * increase border radius ([1d53b40](https://github.com/remvze/pswd/commit/1d53b407cbbaa1ee6445f8daea2d69eab2d938c0)) 64 | * minor change ([fa4c8a4](https://github.com/remvze/pswd/commit/fa4c8a42f5249e113d45dd4bbcb016126dc35720)) 65 | * minor change ([920d44e](https://github.com/remvze/pswd/commit/920d44ec6d69a55e8dfa4f4e18fec866b9428fcc)) 66 | * minor changes ([04bbcf5](https://github.com/remvze/pswd/commit/04bbcf579888a7934367146c3e374ef9e416663f)) 67 | * minor changes ([804f292](https://github.com/remvze/pswd/commit/804f292111c1e62ef7c3f82f9995cabebf3dec72)) 68 | * minor changes ([6b02ac7](https://github.com/remvze/pswd/commit/6b02ac79f0675ddb7a16f4b48147c5ae1c3462aa)) 69 | * minor changes ([6c9a39d](https://github.com/remvze/pswd/commit/6c9a39d0475dcc5d0fb238b33bc046caeea5ab06)) 70 | * new direction ([d9e5096](https://github.com/remvze/pswd/commit/d9e5096f4638511916f73803abd38e89a4302028)) 71 | * remove extra margin ([2a00467](https://github.com/remvze/pswd/commit/2a00467e0abaa4237bf6871a8817df8e3a3126c4)) 72 | * remove underline ([57166b5](https://github.com/remvze/pswd/commit/57166b562fb13de3f3cf5a52c86da910ede0242c)) 73 | 74 | 75 | ### 🚚 Chores 76 | 77 | * remove extra section ([5aad39d](https://github.com/remvze/pswd/commit/5aad39d1997a64e1f39415315e26334fc9e03632)) 78 | * remove extras ([615bd1e](https://github.com/remvze/pswd/commit/615bd1ea9cb0b8de84dcbce987d09b9ed6522dbb)) 79 | * rename ([4411890](https://github.com/remvze/pswd/commit/4411890feb5f24d43c679d895757b06a1816f2b6)) 80 | 81 | ### [0.0.8](https://github.com/remvze/pswd/compare/v0.0.7...v0.0.8) (2024-10-23) 82 | 83 | 84 | ### 🐛 Bug Fixes 85 | 86 | * turn the word into string ([38fcdb1](https://github.com/remvze/pswd/commit/38fcdb10b4c2139cd45b46fc32df257701da991a)) 87 | 88 | 89 | ### ✨ Features 90 | 91 | * add random letter capitalization ([c3e3420](https://github.com/remvze/pswd/commit/c3e34200e4e8e194693dde5792814759c12a62aa)) 92 | * add random numbers for passphrases ([d3a97bc](https://github.com/remvze/pswd/commit/d3a97bce329b8eb08654d7a48960faf8e7c3f8c2)) 93 | * add random symbol separator ([932d54f](https://github.com/remvze/pswd/commit/932d54fc9739fb9d2ba524273eaffc6acbbeaaaa)) 94 | 95 | ### [0.0.7](https://github.com/remvze/pswd/compare/v0.0.6...v0.0.7) (2024-10-05) 96 | 97 | 98 | ### ✨ Features 99 | 100 | * add custom wordlist ([7bb6caa](https://github.com/remvze/pswd/commit/7bb6caa1df4535842995f5264eefcd81d637e292)) 101 | * get length and word count from url params ([9ba2f17](https://github.com/remvze/pswd/commit/9ba2f17f98aba54c3fb525bd932ca54f91c5e2a3)) 102 | 103 | ### [0.0.6](https://github.com/remvze/pswd/compare/v0.0.5...v0.0.6) (2024-09-28) 104 | 105 | 106 | ### ✨ Features 107 | 108 | * add strength score ([54782ac](https://github.com/remvze/pswd/commit/54782ac49c3407dd1f601c2553eb3e3559ff4105)) 109 | 110 | ### [0.0.5](https://github.com/remvze/pswd/compare/v0.0.4...v0.0.5) (2024-09-28) 111 | 112 | 113 | ### 💄 Styling 114 | 115 | * truncate years ([ea6cd3f](https://github.com/remvze/pswd/commit/ea6cd3f5b874d3e6fb569b254bb796db7246bef4)) 116 | 117 | ### [0.0.4](https://github.com/remvze/pswd/compare/v0.0.3...v0.0.4) (2024-09-28) 118 | 119 | 120 | ### ✨ Features 121 | 122 | * add estimated crack time ([7c5c25a](https://github.com/remvze/pswd/commit/7c5c25af52d14f8b8c837203027522918d8ab988)) 123 | 124 | ### [0.0.3](https://github.com/remvze/pswd/compare/v0.0.2...v0.0.3) (2024-09-27) 125 | 126 | 127 | ### ♻️ Code Refactoring 128 | 129 | * separate cryptography related helper ([a25a9ef](https://github.com/remvze/pswd/commit/a25a9ef16ce5c3e0b8b60c5a768bfd7fc6d4ed4d)) 130 | 131 | 132 | ### ✨ Features 133 | 134 | * add crypto explanation ([277a812](https://github.com/remvze/pswd/commit/277a8124091d66fe3e324427ee98f93b10a419e5)) 135 | * add link to the cryptography helper file ([1b9b888](https://github.com/remvze/pswd/commit/1b9b888c791a8f292e488eaa09b1c3e35a727c10)) 136 | 137 | 138 | ### 💄 Styling 139 | 140 | * add accent to donation link ([7573a31](https://github.com/remvze/pswd/commit/7573a310375152e725892dc6d388546e81d25b32)) 141 | 142 | ### [0.0.2](https://github.com/remvze/pswd/compare/v0.0.1...v0.0.2) (2024-09-27) 143 | 144 | 145 | ### ✨ Features 146 | 147 | * add exclude symbols ([39a3de6](https://github.com/remvze/pswd/commit/39a3de6b100dec86dd722fa0ff671157fe7888f0)) 148 | 149 | ### 0.0.1 (2024-09-27) 150 | 151 | 152 | ### ✨ Features 153 | 154 | * add complete wordlist ([4cc0533](https://github.com/remvze/pswd/commit/4cc0533feb688e3b7b515a4bbbf24fba971675e8)) 155 | * add description ([816727d](https://github.com/remvze/pswd/commit/816727daffde22c3ea0394d3fdab977a9a86d883)) 156 | * add donation link ([2a9bb62](https://github.com/remvze/pswd/commit/2a9bb6295e92b467534971d7f4d6b0274ac5a415)) 157 | * add footer ([6f52af3](https://github.com/remvze/pswd/commit/6f52af36de92e19b55ee729cb32223239a6212dc)) 158 | * add hero ([7005102](https://github.com/remvze/pswd/commit/7005102dba7a4d98d639ad312b6cd66fd001dddd)) 159 | * implement the base functionality ([f4721c0](https://github.com/remvze/pswd/commit/f4721c05344d618fe1fc2d628aa331ae3aa26b61)) 160 | * increase maximum words ([20b0d01](https://github.com/remvze/pswd/commit/20b0d01e0e1c1a0cca35eb1959690d06dcc405d7)) 161 | * initial commit ([8d3ec4e](https://github.com/remvze/pswd/commit/8d3ec4ecc3c0ce1d2ed0546cd84b7b8309170fec)) 162 | * minor changes ([15207d5](https://github.com/remvze/pswd/commit/15207d597027fdfaf36df35caff5d554ff767510)) 163 | * replace diceware word ([8f5af3c](https://github.com/remvze/pswd/commit/8f5af3c32749d1c33fa8a4b17d5254bd23b1efa1)) 164 | * save every setting in local storage ([d68a734](https://github.com/remvze/pswd/commit/d68a734feef09fd2d49a69ceab55cc939d831cd8)) 165 | 166 | 167 | ### 🚚 Chores 168 | 169 | * change og and favicon ([6ab7224](https://github.com/remvze/pswd/commit/6ab7224bf51de7411cfae8f1aa8de1a9b85b89d2)) 170 | 171 | 172 | ### 💄 Styling 173 | 174 | * change accent color ([f6f37ac](https://github.com/remvze/pswd/commit/f6f37ac13bb04d770c7dd6c7e3888b77b9dac9f2)) 175 | * decrease padding ([3d0b9c9](https://github.com/remvze/pswd/commit/3d0b9c9701a7c6aff1d26eabbc7aa1302c7ba940)) 176 | -------------------------------------------------------------------------------- /Caddyfile: -------------------------------------------------------------------------------- 1 | :8080 { 2 | file_server 3 | root * /var/www/html 4 | 5 | handle_errors { 6 | rewrite * /index.html 7 | file_server 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/node:20-alpine3.18 AS build 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | RUN npm run build 7 | 8 | FROM docker.io/caddy:latest 9 | COPY ./Caddyfile /etc/caddy/Caddyfile 10 | COPY --from=build /app/dist /var/www/html 11 | 12 | EXPOSE 8080 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MAZE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

PSWD

3 |

Simple secure password generator.

4 | Visit PSWD | Buy Me a Coffee 5 |
6 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import react from '@astrojs/react'; 3 | 4 | import sitemap from '@astrojs/sitemap'; 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | integrations: [react(), sitemap()], 9 | site: 'https://pswd.mvze.net', 10 | }); 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | timesy: 3 | image: ghcr.io/remvze/pswd 4 | logging: 5 | options: 6 | max-size: 1g 7 | restart: always 8 | ports: 9 | - '8080:8080' 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pswd", 3 | "type": "module", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro", 11 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx,.astro", 12 | "lint:fix": "npm run lint -- --fix", 13 | "lint:style": "stylelint ./**/*.{css,astro,html}", 14 | "lint:style:fix": "npm run lint:style -- --fix", 15 | "format": "prettier . --write", 16 | "prepare": "husky install", 17 | "commit": "git-cz", 18 | "release": "standard-version --no-verify", 19 | "release:major": "npm run release -- --release-as major", 20 | "release:minor": "npm run release -- --release-as minor", 21 | "release:patch": "npm run release -- --release-as patch" 22 | }, 23 | "dependencies": { 24 | "@astrojs/react": "^3.0.9", 25 | "@astrojs/sitemap": "3.1.6", 26 | "@types/react": "^18.2.48", 27 | "@types/react-dom": "^18.2.18", 28 | "astro": "^4.2.1", 29 | "framer-motion": "11.5.4", 30 | "radix-ui": "1.1.3", 31 | "react": "^18.2.0", 32 | "react-dom": "^18.2.0", 33 | "react-icons": "5.0.1", 34 | "zxcvbn": "4.4.2" 35 | }, 36 | "devDependencies": { 37 | "@commitlint/cli": "18.4.4", 38 | "@commitlint/config-conventional": "18.4.4", 39 | "@types/zxcvbn": "4.4.5", 40 | "@typescript-eslint/eslint-plugin": "6.19.0", 41 | "@typescript-eslint/parser": "6.19.0", 42 | "astro-eslint-parser": "0.16.2", 43 | "autoprefixer": "10.4.17", 44 | "clipboardy": "4.0.0", 45 | "commitizen": "4.3.0", 46 | "cz-conventional-changelog": "3.3.0", 47 | "eslint": "8.56.0", 48 | "eslint-config-prettier": "9.1.0", 49 | "eslint-import-resolver-alias": "1.1.2", 50 | "eslint-import-resolver-typescript": "3.6.1", 51 | "eslint-plugin-astro": "0.31.3", 52 | "eslint-plugin-import": "2.29.1", 53 | "eslint-plugin-jsx-a11y": "6.8.0", 54 | "eslint-plugin-prettier": "5.1.3", 55 | "eslint-plugin-react": "7.33.2", 56 | "eslint-plugin-react-hooks": "4.6.0", 57 | "eslint-plugin-sort-destructure-keys": "1.5.0", 58 | "eslint-plugin-sort-keys-fix": "1.1.2", 59 | "eslint-plugin-typescript-sort-keys": "3.1.0", 60 | "husky": "8.0.3", 61 | "lint-staged": "15.2.0", 62 | "postcss-html": "1.6.0", 63 | "postcss-nesting": "12.0.2", 64 | "prettier": "3.2.4", 65 | "prettier-plugin-astro": "0.13.0", 66 | "standard-version": "9.5.0", 67 | "stylelint": "16.2.0", 68 | "stylelint-config-html": "1.1.0", 69 | "stylelint-config-recess-order": "4.4.0", 70 | "stylelint-config-standard": "36.0.0", 71 | "stylelint-prettier": "5.0.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | 'postcss-nesting': {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/fonts/bebas-neue-v14-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/fonts/bebas-neue-v14-latin-regular.woff2 -------------------------------------------------------------------------------- /public/fonts/inter-tight-v7-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/fonts/inter-tight-v7-latin-500.woff2 -------------------------------------------------------------------------------- /public/fonts/inter-tight-v7-latin-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/fonts/inter-tight-v7-latin-600.woff2 -------------------------------------------------------------------------------- /public/fonts/inter-tight-v7-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/fonts/inter-tight-v7-latin-700.woff2 -------------------------------------------------------------------------------- /public/fonts/inter-v13-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/fonts/inter-v13-latin-500.woff2 -------------------------------------------------------------------------------- /public/fonts/inter-v13-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/fonts/inter-v13-latin-regular.woff2 -------------------------------------------------------------------------------- /public/fonts/space-mono-v13-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/fonts/space-mono-v13-latin-700.woff2 -------------------------------------------------------------------------------- /public/fonts/space-mono-v13-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/fonts/space-mono-v13-latin-regular.woff2 -------------------------------------------------------------------------------- /public/images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/images/noise.png -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remvze/pswd/b44e2a2be00cc6d9e8c1cceda3aebdd458e2ca92/public/og.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | 4 | Sitemap: https://pswd.mvze.net/sitemap-index.xml 5 | -------------------------------------------------------------------------------- /src/components/app/app.module.css: -------------------------------------------------------------------------------- 1 | .generator { 2 | & .tabs { 3 | display: flex; 4 | column-gap: 8px; 5 | align-items: center; 6 | height: 50px; 7 | padding: 4px; 8 | margin-bottom: 16px; 9 | border: 1px solid var(--color-neutral-100); 10 | border-radius: 12px; 11 | 12 | & button { 13 | display: flex; 14 | flex-grow: 1; 15 | align-items: center; 16 | justify-content: center; 17 | height: 100%; 18 | font-size: var(--font-xsm); 19 | color: var(--color-foreground-subtle); 20 | cursor: pointer; 21 | background-color: transparent; 22 | border: 1px solid var(--color-neutral-50); 23 | border-radius: 8px; 24 | outline: none; 25 | transition: 0.2s; 26 | 27 | &:hover { 28 | border-color: var(--color-neutral-100); 29 | } 30 | 31 | &.active { 32 | position: relative; 33 | font-weight: 500; 34 | color: var(--color-foreground); 35 | background: linear-gradient( 36 | var(--color-neutral-50), 37 | var(--color-neutral-100) 38 | ); 39 | border: 1px solid var(--color-neutral-100); 40 | 41 | &::before { 42 | position: absolute; 43 | bottom: -1px; 44 | left: 50%; 45 | width: 70%; 46 | height: 1px; 47 | content: ''; 48 | background: linear-gradient( 49 | 90deg, 50 | transparent, 51 | var(--color-neutral-400), 52 | transparent 53 | ); 54 | transform: translateX(-50%); 55 | } 56 | 57 | &::after { 58 | position: absolute; 59 | bottom: -6px; 60 | left: 50%; 61 | width: 80%; 62 | height: 1px; 63 | content: ''; 64 | background: linear-gradient( 65 | 90deg, 66 | transparent, 67 | var(--color-neutral-300), 68 | transparent 69 | ); 70 | transform: translateX(-50%); 71 | } 72 | } 73 | } 74 | } 75 | 76 | & .resultWrapper { 77 | display: flex; 78 | column-gap: 8px; 79 | align-items: center; 80 | height: 50px; 81 | margin-bottom: 16px; 82 | 83 | & .score { 84 | display: flex; 85 | align-items: flex-end; 86 | width: 3px; 87 | height: 70%; 88 | overflow: hidden; 89 | background-color: var(--color-neutral-100); 90 | border-radius: 50px; 91 | 92 | & .filled { 93 | width: 100%; 94 | background-color: transparent; 95 | transition: 0.2s; 96 | } 97 | } 98 | 99 | & .result { 100 | position: relative; 101 | display: flex; 102 | flex-grow: 1; 103 | column-gap: 4px; 104 | align-items: center; 105 | width: 100%; 106 | height: 100%; 107 | padding: 4px; 108 | background: linear-gradient( 109 | 90deg, 110 | var(--color-neutral-100), 111 | transparent, 112 | transparent, 113 | transparent 114 | ); 115 | border: 1px solid var(--color-neutral-200); 116 | border-radius: 12px; 117 | 118 | &::before { 119 | position: absolute; 120 | top: -1px; 121 | left: 10px; 122 | width: 40%; 123 | height: 1px; 124 | content: ''; 125 | background: linear-gradient( 126 | 90deg, 127 | transparent, 128 | var(--color-neutral-400), 129 | transparent, 130 | transparent 131 | ); 132 | } 133 | 134 | &::after { 135 | position: absolute; 136 | right: 10px; 137 | bottom: -1px; 138 | width: 40%; 139 | height: 1px; 140 | content: ''; 141 | background: linear-gradient( 142 | 90deg, 143 | transparent, 144 | transparent, 145 | var(--color-neutral-400), 146 | transparent 147 | ); 148 | } 149 | 150 | & input { 151 | flex-grow: 1; 152 | width: 100%; 153 | min-width: 0; 154 | padding: 0 0 0 8px; 155 | font-family: var(--font-mono); 156 | font-size: var(--font-sm); 157 | color: var(--color-foreground); 158 | background-color: transparent; 159 | border: none; 160 | outline: none; 161 | } 162 | 163 | & button { 164 | display: flex; 165 | align-items: center; 166 | justify-content: center; 167 | height: 100%; 168 | aspect-ratio: 1 / 1; 169 | color: var(--color-foreground-subtle); 170 | cursor: pointer; 171 | background-color: var(--color-neutral-100); 172 | border: none; 173 | border-radius: 8px; 174 | outline: none; 175 | 176 | &.generate { 177 | color: var(--color-neutral-50); 178 | background-color: var(--color-neutral-950); 179 | box-shadow: inset 0 -3px 0 var(--color-neutral-700); 180 | } 181 | 182 | &.hide { 183 | width: 30px; 184 | font-size: var(--font-sm); 185 | color: var(--color-foreground-subtler); 186 | background-color: transparent; 187 | } 188 | } 189 | } 190 | } 191 | 192 | & .crackTime { 193 | position: relative; 194 | padding: 12px 0 12px 12px; 195 | margin-bottom: 16px; 196 | font-size: var(--font-sm); 197 | line-height: 1.6; 198 | color: var(--color-foreground-subtle); 199 | background: linear-gradient( 200 | 90deg, 201 | var(--color-neutral-100), 202 | transparent, 203 | transparent 204 | ); 205 | border-left: 1px solid var(--color-neutral-200); 206 | border-radius: 8px; 207 | 208 | &::before { 209 | position: absolute; 210 | top: 50%; 211 | left: -1px; 212 | width: 1px; 213 | height: 80%; 214 | content: ''; 215 | background: linear-gradient( 216 | transparent, 217 | var(--color-neutral-400), 218 | transparent 219 | ); 220 | transform: translateY(-50%); 221 | } 222 | 223 | & .time { 224 | display: flex; 225 | column-gap: 8px; 226 | align-items: center; 227 | 228 | & .label { 229 | min-width: max-content; 230 | } 231 | 232 | & .truncate { 233 | display: inline-block; 234 | flex-grow: 1; 235 | overflow: hidden; 236 | text-overflow: ellipsis; 237 | white-space: nowrap; 238 | vertical-align: bottom; 239 | } 240 | } 241 | 242 | & .mono { 243 | font-family: var(--font-mono); 244 | color: var(--color-foreground); 245 | } 246 | 247 | & .attempts { 248 | margin-top: 8px; 249 | font-size: var(--font-xsm); 250 | 251 | & .mono { 252 | & .accent { 253 | color: var(--color-foreground-subtler); 254 | } 255 | 256 | & sup { 257 | font-size: var(--font-2xsm); 258 | color: var(--color-foreground-subtle); 259 | } 260 | } 261 | 262 | & .text { 263 | padding-left: 4px; 264 | 265 | & span { 266 | color: var(--color-foreground-subtler); 267 | } 268 | } 269 | } 270 | } 271 | 272 | & .tabContent { 273 | position: relative; 274 | padding: 20px; 275 | background-color: var(--color-neutral-50); 276 | border: 1px solid var(--color-neutral-100); 277 | border-radius: 8px; 278 | 279 | &::before { 280 | position: absolute; 281 | bottom: -10px; 282 | left: 50%; 283 | z-index: -2; 284 | width: 90%; 285 | height: 50px; 286 | content: ''; 287 | background-color: var(--color-neutral-50); 288 | border: 1px solid var(--color-neutral-100); 289 | border-radius: 8px; 290 | transform: translateX(-50%); 291 | } 292 | 293 | &::after { 294 | position: absolute; 295 | bottom: -20px; 296 | left: 50%; 297 | z-index: -3; 298 | width: 80%; 299 | height: 50px; 300 | content: ''; 301 | background-color: var(--color-neutral-50); 302 | border: 1px solid var(--color-neutral-100); 303 | border-radius: 8px; 304 | transform: translateX(-50%); 305 | } 306 | 307 | & .shineBottom { 308 | position: absolute; 309 | bottom: -1px; 310 | left: 50%; 311 | width: 100%; 312 | height: 1px; 313 | background: linear-gradient( 314 | 90deg, 315 | transparent, 316 | var(--color-neutral-200), 317 | transparent, 318 | transparent, 319 | transparent, 320 | transparent, 321 | var(--color-neutral-200), 322 | transparent 323 | ); 324 | transform: translateX(-50%); 325 | } 326 | 327 | & .shineTop { 328 | position: absolute; 329 | top: -1px; 330 | left: 20px; 331 | width: 80%; 332 | height: 1px; 333 | background: linear-gradient( 334 | 90deg, 335 | transparent, 336 | var(--color-neutral-300), 337 | transparent, 338 | transparent, 339 | transparent 340 | ); 341 | } 342 | 343 | & .controls { 344 | & .length { 345 | margin-bottom: 16px; 346 | 347 | & label { 348 | display: block; 349 | margin-bottom: 8px; 350 | font-size: var(--font-sm); 351 | font-weight: 500; 352 | color: var(--color-foreground-subtle); 353 | } 354 | 355 | & .inputs { 356 | display: flex; 357 | column-gap: 12px; 358 | align-items: center; 359 | 360 | & input[type='number'] { 361 | width: 70px; 362 | min-width: 70px; 363 | height: 36px; 364 | padding: 0 4px; 365 | font-family: var(--font-mono); 366 | font-size: var(--font-sm); 367 | font-weight: 500; 368 | color: var(--color-foreground); 369 | text-align: center; 370 | background-color: var(--color-neutral-100); 371 | border: 1px solid var(--color-neutral-200); 372 | border-radius: 8px; 373 | outline: none; 374 | } 375 | 376 | & input[type='range'] { 377 | flex-grow: 1; 378 | width: 100%; 379 | min-width: 0; 380 | accent-color: #a3e635; 381 | } 382 | } 383 | } 384 | 385 | & .separator { 386 | margin-top: 16px; 387 | 388 | & label { 389 | display: block; 390 | margin-bottom: 8px; 391 | font-size: var(--font-sm); 392 | font-weight: 500; 393 | color: var(--color-foreground-subtle); 394 | } 395 | 396 | & select { 397 | display: block; 398 | width: 100%; 399 | min-width: 0; 400 | height: 40px; 401 | padding: 0 8px; 402 | font-size: var(--font-sm); 403 | font-weight: 500; 404 | color: var(--color-foreground); 405 | background-color: var(--color-neutral-100); 406 | border: 1px solid var(--color-neutral-200); 407 | border-radius: 8px; 408 | outline: none; 409 | } 410 | } 411 | 412 | & .customWordlist { 413 | margin-top: 16px; 414 | 415 | & label { 416 | display: block; 417 | margin-bottom: 8px; 418 | font-size: var(--font-sm); 419 | font-weight: 500; 420 | color: var(--color-foreground-subtle); 421 | 422 | & span { 423 | font-weight: 400; 424 | color: var(--color-foreground-subtler); 425 | } 426 | } 427 | 428 | & textarea { 429 | display: block; 430 | width: 100%; 431 | min-width: 0; 432 | height: 160px; 433 | min-height: 160px; 434 | padding: 8px; 435 | font-size: var(--font-sm); 436 | font-weight: 500; 437 | color: var(--color-foreground); 438 | resize: vertical; 439 | background-color: var(--color-neutral-100); 440 | border: 1px solid var(--color-neutral-200); 441 | border-radius: 8px; 442 | outline: none; 443 | } 444 | } 445 | 446 | & .custom { 447 | margin-top: 16px; 448 | 449 | & label { 450 | display: block; 451 | margin-bottom: 8px; 452 | font-size: var(--font-sm); 453 | font-weight: 500; 454 | color: var(--color-foreground-subtle); 455 | } 456 | 457 | & input { 458 | display: block; 459 | width: 100%; 460 | min-width: 0; 461 | height: 40px; 462 | padding: 0 8px; 463 | font-family: var(--font-mono); 464 | font-size: var(--font-sm); 465 | color: var(--color-foreground); 466 | background-color: var(--color-neutral-100); 467 | border: 1px solid var(--color-neutral-200); 468 | border-radius: 8px; 469 | outline: none; 470 | } 471 | } 472 | 473 | & .checkbox { 474 | display: flex; 475 | column-gap: 8px; 476 | align-items: center; 477 | font-size: var(--font-sm); 478 | color: var(--color-foreground-subtle); 479 | 480 | &:not(:last-of-type) { 481 | margin-bottom: 12px; 482 | } 483 | 484 | & input { 485 | accent-color: #a3e635; 486 | } 487 | } 488 | } 489 | } 490 | } 491 | 492 | .donate { 493 | margin-top: 40px; 494 | font-size: var(--font-xsm); 495 | color: var(--color-foreground-subtle); 496 | text-align: center; 497 | 498 | & a { 499 | font-weight: 500; 500 | color: var(--color-foreground); 501 | text-decoration-style: dotted; 502 | text-decoration-color: var(--color-foreground-subtler); 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /src/components/app/app.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useCallback, useMemo } from 'react'; 2 | import { 3 | FaRegCopy, 4 | FaArrowRotateLeft, 5 | FaCheck, 6 | FaRegEye, 7 | FaRegEyeSlash, 8 | } from 'react-icons/fa6'; 9 | import zxcvbn from 'zxcvbn'; 10 | 11 | import { Container } from '../container'; 12 | import { Slider } from '../slider'; 13 | import { Checkbox } from '../checkbox'; 14 | 15 | import { useCopy } from '@/hooks/use-copy'; 16 | import { useLocalStorage } from '@/hooks/use-local-storage'; 17 | import { 18 | getSecureRandomInt, 19 | getSecureRandomIntInRange, 20 | } from '@/helpers/crypto'; 21 | import { capitalizeString } from '@/helpers/string'; 22 | 23 | import { wordlist } from '@/data/wordlist'; 24 | 25 | import styles from './app.module.css'; 26 | import { cn } from '@/helpers/styles'; 27 | import { formatSeconds } from '@/helpers/time'; 28 | 29 | const WORDLIST = wordlist; 30 | 31 | export function App() { 32 | const [activeTab, setActiveTab] = useLocalStorage< 33 | 'normal' | 'diceware' | 'pin' 34 | >('pswd-active-tab', 'normal'); 35 | const { copy, copying } = useCopy(); 36 | const [showPassword, setShowPassword] = useLocalStorage( 37 | 'pswd-show-password', 38 | true, 39 | ); 40 | 41 | const [password, setPassword] = useState(''); 42 | const [length, setLength] = useLocalStorage('pswd-length', 12); 43 | const [includeUpper, setIncludeUpper] = useLocalStorage( 44 | 'pswd-include-upper', 45 | true, 46 | ); 47 | const [includeLower, setIncludeLower] = useLocalStorage( 48 | 'pswd-include-lower', 49 | true, 50 | ); 51 | const [includeNumbers, setIncludeNumbers] = useLocalStorage( 52 | 'pswd-include-numbers', 53 | true, 54 | ); 55 | const [includeSymbols, setIncludeSymbols] = useLocalStorage( 56 | 'pswd-include-symbols', 57 | true, 58 | ); 59 | const [excludeSimilar, setExcludeSimilar] = useLocalStorage( 60 | 'pswd-exclude-similar', 61 | false, 62 | ); 63 | const [customSymbols, setCustomSymbols] = useLocalStorage( 64 | 'pswd-custom-symbols', 65 | '', 66 | ); 67 | const [excludeSymbols, setExcludeSymbols] = useLocalStorage( 68 | 'pswd-exclude-symbols', 69 | '', 70 | ); 71 | 72 | const [wordCount, setWordCount] = useLocalStorage('pswd-word-count', 6); 73 | const [separator, setSeparator] = useLocalStorage('pswd-separator', 'space'); 74 | const [capitalize, setCapitalize] = useLocalStorage('pswd-capitalize', false); 75 | const [randomCapitalization, setRandomCapitalization] = useLocalStorage( 76 | 'pswd-random-capitalization', 77 | false, 78 | ); 79 | const [randomNumberBeginning, setRandomNumberBeginning] = useLocalStorage( 80 | 'pswd-random-number-beginning', 81 | false, 82 | ); 83 | const [randomNumberEnd, setRandomNumberEnd] = useLocalStorage( 84 | 'pswd-random-number-end', 85 | false, 86 | ); 87 | const [customWordlist, setCustomWordlist] = useLocalStorage( 88 | 'pswd-custom-wordlist', 89 | '', 90 | ); 91 | 92 | const [pinLength, setPinLength] = useLocalStorage('pswd-pin-length', 6); 93 | 94 | useEffect(() => { 95 | const urlParams = new URLSearchParams(window.location.search); 96 | const length = Number(urlParams.get('length')); 97 | const words = Number(urlParams.get('words')); 98 | 99 | if (length > 0) { 100 | setLength(length); 101 | setActiveTab('normal'); 102 | } else if (words > 0) { 103 | setWordCount(words); 104 | setActiveTab('diceware'); 105 | } 106 | }, [setLength, setActiveTab, setWordCount]); 107 | 108 | const wordlist = useMemo(() => { 109 | const custom = customWordlist 110 | .split('\n') 111 | .map(item => item.trim()) 112 | .filter(item => !!item); 113 | 114 | if (custom.length > 0) return custom; 115 | return WORDLIST; 116 | }, [customWordlist]); 117 | 118 | const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 119 | const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'; 120 | const NUMBERS = '0123456789'; 121 | const SYMBOLS = '!@#$%^&*()_+-=[]{}|;:,.<>?'; 122 | 123 | const SIMILAR_CHARACTERS = 'Il1O0'; 124 | 125 | const generatePassword = useCallback(() => { 126 | if (activeTab === 'normal') { 127 | let characterSet = ''; 128 | 129 | if (includeUpper) characterSet += UPPERCASE; 130 | if (includeLower) characterSet += LOWERCASE; 131 | if (includeNumbers) characterSet += NUMBERS; 132 | if (includeSymbols) characterSet += SYMBOLS; 133 | 134 | if (customSymbols) { 135 | characterSet += customSymbols; 136 | } 137 | 138 | let toExclude = ''; 139 | 140 | if (excludeSimilar) { 141 | toExclude += SIMILAR_CHARACTERS; 142 | } 143 | 144 | if (excludeSymbols) { 145 | toExclude += excludeSymbols; 146 | } 147 | 148 | if (toExclude) { 149 | const escaped = toExclude.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); 150 | const regex = new RegExp(`[${escaped}]`, 'g'); 151 | 152 | characterSet = characterSet.replace(regex, ''); 153 | } 154 | 155 | if (characterSet.length === 0) { 156 | setPassword(''); 157 | return; 158 | } 159 | 160 | const passwordCharacters = []; 161 | const charsetLength = characterSet.length; 162 | 163 | for (let i = 0; i < length; i++) { 164 | const randomIndex = getSecureRandomInt(charsetLength); 165 | passwordCharacters.push(characterSet[randomIndex]); 166 | } 167 | 168 | const newPassword = passwordCharacters.join(''); 169 | setPassword(newPassword); 170 | } else if (activeTab === 'diceware') { 171 | if (wordlist.length === 0) { 172 | alert('Wordlist is empty. Please provide a valid wordlist.'); 173 | return; 174 | } 175 | 176 | let words: Array = []; 177 | const wordlistLength = wordlist.length; 178 | 179 | for (let i = 0; i < wordCount; i++) { 180 | const index = getSecureRandomInt(wordlistLength); 181 | const word = wordlist[index]; 182 | 183 | words.push(capitalize ? capitalizeString(word) : word); 184 | } 185 | 186 | if (randomCapitalization) { 187 | words = words.map(word => { 188 | const newWord = String(word) 189 | .split('') 190 | .map(letter => 191 | Math.random() > 0.5 ? letter.toLowerCase() : letter.toUpperCase(), 192 | ) 193 | .join(''); 194 | 195 | return newWord; 196 | }); 197 | } 198 | 199 | if (randomNumberBeginning) { 200 | const randomNumber = getSecureRandomIntInRange(100, 999); 201 | 202 | words.unshift(randomNumber); 203 | } 204 | 205 | if (randomNumberEnd) { 206 | const randomNumber = getSecureRandomIntInRange(100, 999); 207 | 208 | words.push(randomNumber); 209 | } 210 | 211 | if (separator === 'symbol') { 212 | const last = words.pop(); 213 | 214 | words = words.map(word => { 215 | const randomSymbol = SYMBOLS[getSecureRandomInt(SYMBOLS.length)]; 216 | 217 | return word + randomSymbol; 218 | }); 219 | 220 | words.push(last); 221 | 222 | setPassword(words.filter(Boolean).join('')); 223 | } else { 224 | setPassword( 225 | words.join( 226 | separator === 'space' ? ' ' : separator === 'dash' ? '-' : '', 227 | ), 228 | ); 229 | } 230 | } else if (activeTab === 'pin') { 231 | const passwordCharacters = []; 232 | const charsetLength = NUMBERS.length; 233 | 234 | for (let i = 0; i < pinLength; i++) { 235 | const randomIndex = getSecureRandomInt(charsetLength); 236 | passwordCharacters.push(NUMBERS[randomIndex]); 237 | } 238 | 239 | const newPassword = passwordCharacters.join(''); 240 | setPassword(newPassword); 241 | } 242 | }, [ 243 | pinLength, 244 | includeUpper, 245 | randomCapitalization, 246 | randomNumberBeginning, 247 | randomNumberEnd, 248 | includeLower, 249 | includeNumbers, 250 | includeSymbols, 251 | length, 252 | wordCount, 253 | activeTab, 254 | separator, 255 | excludeSimilar, 256 | customSymbols, 257 | capitalize, 258 | excludeSymbols, 259 | wordlist, 260 | ]); 261 | 262 | useEffect(() => { 263 | generatePassword(); 264 | }, [activeTab, generatePassword]); 265 | 266 | const [crackTime, setCrackTime] = useState(''); 267 | const [strength, setStrength] = useState(0); 268 | const strenthColor = [ 269 | 'transparent', 270 | '#ef4444', 271 | '#f97316', 272 | '#eab308', 273 | '#65a30d', 274 | '#22c55e', 275 | ][strength]; 276 | 277 | useEffect(() => { 278 | if (password) { 279 | const result = zxcvbn(password); 280 | 281 | setCrackTime( 282 | formatSeconds( 283 | result.crack_times_seconds 284 | .offline_fast_hashing_1e10_per_second as number, 285 | ), 286 | ); 287 | 288 | setStrength(result.score + 1); 289 | } else { 290 | setCrackTime(''); 291 | setStrength(0); 292 | } 293 | }, [password]); 294 | 295 | return ( 296 | 297 |
298 |
299 | 305 | 311 | 317 |
318 | 319 |
320 | {activeTab !== 'pin' && ( 321 |
322 |
329 |
330 | )} 331 | 332 |
333 | 338 | 344 | 347 | 353 |
354 |
355 | 356 | {crackTime && activeTab !== 'pin' && ( 357 |
358 |

359 | Crack Time: 360 | 361 | {crackTime} 362 | 363 |

364 | 365 |

366 | 367 | * 1010 368 | {' '} 369 | 370 | hash attempts / second 371 | 372 |

373 |
374 | )} 375 | 376 | {activeTab === 'normal' && ( 377 |
378 |
379 |
380 | 381 |
382 |
383 | 384 |
385 | setLength(Number(e.target.value))} 392 | /> 393 | 394 | setLength(value)} 399 | /> 400 |
401 |
402 | 403 | 410 | 411 | 418 | 419 | 426 | 427 | 434 | 435 | 442 | 443 |
444 | 445 | setCustomSymbols(e.target.value)} 451 | /> 452 |
453 | 454 |
455 | 456 | setExcludeSymbols(e.target.value)} 462 | /> 463 |
464 |
465 |
466 | )} 467 | 468 | {activeTab === 'diceware' && ( 469 |
470 |
471 |
472 | 473 |
474 |
475 | 476 | 477 |
478 | setWordCount(Number(e.target.value))} 485 | /> 486 | 487 | setWordCount(value)} 492 | /> 493 |
494 |
495 | 496 | 503 | 504 | 511 | 512 | 519 | 520 | 527 | 528 |
529 | 530 | 539 |
540 | 541 |
542 | 545 |