├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ ├── doc_issue.yml │ ├── features.yml │ └── other.yml ├── pull_request_template.md └── workflows │ ├── compliance.yml │ ├── deployment.yml │ ├── label-commenter-config.yml │ ├── label-commenter.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── .vaunt └── config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── bi_arrow-right-square-fill.svg ├── close-square-fill.svg ├── favicon.ico └── open-graph.webp ├── index.html ├── package-lock.json ├── package.json ├── src ├── eggy-js.d.ts ├── lib │ ├── getElements.ts │ └── packages │ │ ├── helpers.ts │ │ └── utils.ts ├── main.ts ├── pages │ ├── animation.ts │ ├── border-radius.ts │ ├── box-shadow.ts │ ├── gradient-background.ts │ ├── gradient-border.ts │ ├── gradient-text.ts │ ├── grid-generator.ts │ ├── input-range.ts │ ├── pic-text.ts │ ├── scroll.ts │ ├── text-shadow.ts │ └── transform.ts ├── style.css └── vite-env.d.ts ├── tsconfig.json ├── typedoc.json └── vite.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "prettier" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": "latest", 14 | "sourceType": "module" 15 | }, 16 | "plugins": ["@typescript-eslint"], 17 | "rules": { 18 | "@typescript-eslint/no-unused-vars": "error", 19 | 20 | "@typescript-eslint/consistent-type-definitions": ["error", "type"] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug 2 | description: Report an issue to help improve the project. 3 | title: "[BUG] " 4 | labels: ["bug", "hacktoberfast", "🚦 status: awaiting approval"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A brief description of the question or issue, also include what you tried and what didn't work 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshots 15 | attributes: 16 | label: Screenshots 17 | description: Please add screenshots if applicable 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: extrainfo 22 | attributes: 23 | label: Additional information 24 | description: Is there anything else we should know about this bug? 25 | validations: 26 | required: false 27 | 28 | - type: dropdown 29 | id: browser 30 | attributes: 31 | label: "🥦 Browser" 32 | description: "What browser are you using ?" 33 | options: 34 | - Google Chrome 35 | - Brave 36 | - Microsoft Edge 37 | - Mozilla Firefox 38 | - Safari 39 | - Opera 40 | - Other 41 | validations: 42 | required: true 43 | 44 | - type: checkboxes 45 | id: no-duplicate-issues 46 | attributes: 47 | label: "👀 Have you checked if this issue has been raised before?" 48 | options: 49 | - label: "I checked and didn't find a similar issue" 50 | required: true 51 | 52 | - type: checkboxes 53 | id: read-code-of-conduct 54 | attributes: 55 | label: "🏢 Have you read the Contributing Guidelines?" 56 | options: 57 | - label: "I have read and understood the rules in the [Contributing Guidelines](https://github.com/Dun-sin/Code-Magic/blob/main/CONTRIBUTING.md)" 58 | required: true 59 | 60 | - type: dropdown 61 | attributes: 62 | label: Are you willing to work on this issue? 63 | description: This is absolutely not required, but we are happy to guide you in the contribution process. 64 | options: 65 | - "Yes I am willing to submit a PR!" 66 | 67 | - type: markdown 68 | attributes: 69 | value: | 70 | Follow me on Twitter [here](https://twitter.com/dunsincodes) 71 | Feel free to Join the discord server [here](https://discord.gg/kcFraw3nhz) and check out other cool repositories [here](https://github.com/Dun-sin) 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🙋🏾🙋🏼Question 4 | url: https://discord.gg/ufcysW9q23 5 | about: Feel free to ask your question on our Discord channel. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/doc_issue.yml: -------------------------------------------------------------------------------- 1 | name: 📄 Documentation issue 2 | description: Found an issue in the documentation? You can use this one! 3 | title: "[DOCS] " 4 | labels: ["documentation", "hacktoberfest", "🚦 status: awaiting approval"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A brief description of the question or issue, also include what you tried and what didn't work 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshots 15 | attributes: 16 | label: Screenshots 17 | description: Please add screenshots if applicable 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: extrainfo 22 | attributes: 23 | label: Additional information 24 | description: Is there anything else we should know about this issue? 25 | validations: 26 | required: false 27 | 28 | - type: checkboxes 29 | id: no-duplicate-issues 30 | attributes: 31 | label: "👀 Have you checked if this issue has been raised before?" 32 | options: 33 | - label: "I checked and didn't find similar issue" 34 | required: true 35 | 36 | - type: checkboxes 37 | id: read-code-of-conduct 38 | attributes: 39 | label: "🏢 Have you read the Contributing Guidelines?" 40 | options: 41 | - label: "I have read and understood the rules in the [Contributing Guidelines](https://github.com/Dun-sin/Code-Magic/blob/main/CONTRIBUTING.md)" 42 | required: true 43 | 44 | - type: dropdown 45 | attributes: 46 | label: Are you willing to work on this issue ? 47 | description: This is absolutely not required, but we are happy to guide you in the contribution process. 48 | options: 49 | - "Yes I am willing to submit a PR!" 50 | 51 | - type: markdown 52 | attributes: 53 | value: | 54 | Follow me on Twitter [here](https://twitter.com/dunsincodes) 55 | Feel free to Join the discord server [here](https://discord.gg/kcFraw3nhz) and check out other cool repositories [here](https://github.com/Dun-sin) 56 | 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/features.yml: -------------------------------------------------------------------------------- 1 | name: 💡 General Feature Request 2 | description: Have a new idea/feature for this project? Would love to hear it! 3 | title: "[FEATURE] " 4 | labels: ["feature","hacktoberfest", "🚦 status: awaiting approval"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A brief description of the enhancement you propose, also include what you tried and what worked. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshots 15 | attributes: 16 | label: Screenshots 17 | description: Please add screenshots if applicable 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: extrainfo 22 | attributes: 23 | label: Additional information 24 | description: Is there anything else we should know about this idea? 25 | validations: 26 | required: false 27 | 28 | - type: checkboxes 29 | id: no-duplicate-issues 30 | attributes: 31 | label: "👀 Have you checked if this issue has been raised before?" 32 | options: 33 | - label: "I checked and didn't find similar issue" 34 | required: true 35 | 36 | - type: checkboxes 37 | id: read-code-of-conduct 38 | attributes: 39 | label: "🏢 Have you read the Contributing Guidelines?" 40 | options: 41 | - label: "I have read and understood the rules in the [Contributing Guidelines](https://github.com/Dun-sin/Code-Magic/blob/main/CONTRIBUTING.md)" 42 | required: true 43 | 44 | - type: dropdown 45 | attributes: 46 | label: Are you willing to work on this issue ? 47 | description: This is absolutely not required, but we are happy to guide you in the contribution process. 48 | options: 49 | - "Yes I am willing to submit a PR!" 50 | 51 | - type: markdown 52 | attributes: 53 | value: | 54 | Follow me on Twitter [here](https://twitter.com/dunsincodes) 55 | Feel free to Join the discord server [here](https://discord.gg/kcFraw3nhz) and check out other cool repositories [here](https://github.com/Dun-sin) 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yml: -------------------------------------------------------------------------------- 1 | name: Other 2 | description: Use this for any other issues. Please do NOT create blank issues 3 | title: "[OTHER]" 4 | labels: ["hacktoberfest", "🚦 status: awaiting approval"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: "# Other issue" 9 | - type: textarea 10 | id: issuedescription 11 | attributes: 12 | label: What would you like to share? 13 | description: Provide a clear and concise explanation of your issue. 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: extrainfo 18 | attributes: 19 | label: Additional information 20 | description: Is there anything else we should know about this issue? 21 | validations: 22 | required: false 23 | 24 | - type: checkboxes 25 | id: no-duplicate-issues 26 | attributes: 27 | label: "👀 Have you checked if this issue has been raised before?" 28 | options: 29 | - label: "I checked and didn't find similar issue" 30 | required: true 31 | 32 | - type: checkboxes 33 | id: read-code-of-conduct 34 | attributes: 35 | label: "🏢 Have you read the Contributing Guidelines?" 36 | options: 37 | - label: "I have read and understood the rules in the [Contributing Guidelines](https://github.com/Dun-sin/Code-Magic/blob/main/CONTRIBUTING.md)" 38 | required: true 39 | 40 | - type: dropdown 41 | attributes: 42 | label: Are you willing to work on this issue ? 43 | description: This is absolutely not required, but we are happy to guide you in the contribution process. 44 | options: 45 | - "Yes I am willing to submit a PR!" 46 | 47 | - type: markdown 48 | attributes: 49 | value: | 50 | Follow me on Twitter [here](https://twitter.com/Dunsincoes) 51 | Feel free to Join the discord server [here](https://discord.gg/kcFraw3nhz) and check out other cool repositories [here](https://github.com/Dun-sin) 52 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Fixes Issue 2 | 3 | **My PR closes #issue_number_here** 4 | 5 | # 👨‍💻 Changes proposed(What did you do ?) 6 | 7 | # ✔️ Check List (Check all the applicable boxes) 8 | 9 | 10 | 15 | 16 | - [] My code follows the code style of this project. 17 | - [] This PR does not contain plagiarized content. 18 | - [] The title and description of the PR is clear and explains the approach. 19 | 20 | ## Note to reviewers 21 | 22 | 23 | 24 | # 📷 Screenshots 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/compliance.yml: -------------------------------------------------------------------------------- 1 | name: Compliance 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | - reopened 10 | workflow_call: 11 | 12 | permissions: 13 | pull-requests: write 14 | 15 | jobs: 16 | pr-compliance-checks: 17 | name: PR Compliance Checks 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: mtfoley/pr-compliance-action@v0.5.0 21 | with: 22 | body-auto-close: false 23 | protected-branch-auto-close: false 24 | watch-files: | 25 | package.json 26 | package-lock.json 27 | body-comment: > 28 | ## Issue Reference 29 | In order to be considered for merging, the pull request description must refer to a 30 | specific issue number. This is described in our 31 | [Contributing Guide](https://github.com/Dun-sin/code-magic/blob/main/CONTRIBUTING.md). 32 | This check is looking for a phrase similar to: "Fixes #XYZ" or "Resolves #XYZ" where XYZ is the issue 33 | number that this PR is meant to address. 34 | -------------------------------------------------------------------------------- /.github/workflows/deployment.yml: -------------------------------------------------------------------------------- 1 | name: deploy website 2 | on: 3 | pull_request_target: 4 | branches: 5 | - main 6 | types: 7 | - closed 8 | 9 | 10 | jobs: 11 | deploy: 12 | if: github.event.pull_request.merged == true 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - run: | 17 | tsc && npm run build 18 | 19 | - uses: amondnet/vercel-action@v20 #deploy 20 | with: 21 | vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required 22 | github-token: ${{ secrets.GENERAL_TOKEN }} #Optional 23 | vercel-args: '--prod' #Optional 24 | vercel-org-id: ${{ secrets.ORG_ID}} #Required 25 | vercel-project-id: ${{ secrets.PROJECT_ID}} #Required 26 | working-directory: ./ 27 | -------------------------------------------------------------------------------- /.github/workflows/label-commenter-config.yml: -------------------------------------------------------------------------------- 1 | labels: 2 | - name: invalid 3 | labeled: 4 | issue: 5 | body: Please follow the issue templates. 6 | action: close 7 | pr: 8 | body: | 9 | Thank you @{{ pull_request.user.login }} for raising a PR. Please follow the pull request templates. 10 | PR compliance is complaining because of the PR description, some things needs to be fixed properly, if you don't know how, try this: 11 | - Read the error carefully or read the contributing file 12 | - Checkout past merged Pull requests to see how they were done for it to be merged 13 | > Note: you can edit your PR message, this PR won't be merged if the error isn't gone and you have a week max to fix it before the PR gets closed without being merged 14 | - name: wontfix 15 | labeled: 16 | issue: 17 | body: This will not be worked on but we appreciate your contribution. 18 | action: close 19 | unlabeled: 20 | issue: 21 | body: This has become active again. 22 | action: open 23 | - name: duplicate 24 | labeled: 25 | issue: 26 | body: This issue already exists. 27 | action: close 28 | - name: '🚦 status: awaiting approval' 29 | labeled: 30 | issue: 31 | body: | 32 | This issue is waiting to be approved so it's locked until approved 33 | Thank you @{{ issue.user.login }} for creating an issue, while you wait for a response, consider reading the Contributing.md file. 34 | If you have any questions, please reach out to us on Discord or follow up on the issue itself. 35 | Thank you! Welcome to the community 36 | locking: lock 37 | - name: '🏁 status: ready for dev' 38 | labeled: 39 | issue: 40 | body: | 41 | This issue is ready for dev and assigned to anyone who properly asks 42 | locking: unlock 43 | -------------------------------------------------------------------------------- /.github/workflows/label-commenter.yml: -------------------------------------------------------------------------------- 1 | name: Label Commenter 2 | 3 | on: 4 | issues: 5 | types: 6 | - labeled 7 | - unlabeled 8 | pull_request_target: 9 | types: 10 | - labeled 11 | - unlabeled 12 | discussion: 13 | types: 14 | - labeled 15 | - unlabeled 16 | 17 | permissions: 18 | contents: read 19 | issues: write 20 | pull-requests: write 21 | 22 | jobs: 23 | comment: 24 | runs-on: ubuntu-20.04 25 | timeout-minutes: 1 26 | steps: 27 | - uses: actions/checkout@v2.3.4 28 | with: 29 | ref: main 30 | 31 | - name: Label Commenter 32 | uses: peaceiris/actions-label-commenter@b9f3f5d91e1f6ea0fd28c45cee43e0b0a687a272 33 | env: 34 | RUNNER_DEBUG: 1 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | config_file: .github/workflows/label-commenter-config.yml 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Changelog 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | release: 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | issues: write 15 | runs-on: ubuntu-latest 16 | if: ${{ github.ref == 'refs/heads/main' }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm ci 24 | - run: npm run semantic-release 25 | env: 26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 27 | GITHUB_TOKEN: ${{ secrets.CHANGELOG_RELEASE }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | *.lock 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "bracketSpacing": false, 6 | "trailingComma": "es5", 7 | "bracketSameLine": false, 8 | "semi": true 9 | } 10 | -------------------------------------------------------------------------------- /.vaunt/config.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0.1 2 | achievements: 3 | - achievement: 4 | name: Shooting Star 5 | icon: https://raw.githubusercontent.com/vauntdev/example/main/.vaunt/shooting_star.png 6 | description: Awarded for starring our repository, make a wish! 7 | triggers: 8 | - trigger: 9 | actor: author 10 | action: star 11 | condition: starred = true 12 | - achievement: 13 | name: Every Bit Counts 14 | icon: https://raw.githubusercontent.com/vauntdev/example/main/.vaunt/every_bit_counts.png 15 | description: No commit is too small! 16 | triggers: 17 | - trigger: 18 | actor: author 19 | action: commit 20 | condition: count() >= 1 21 | - achievement: 22 | name: Pull Request Hero 23 | icon: https://raw.githubusercontent.com/vauntdev/example/main/.vaunt/pull_request_hero.png 24 | description: You're a PR hero, rock on! 25 | triggers: 26 | - trigger: 27 | actor: author 28 | action: pull_request 29 | condition: merged = true 30 | - achievement: 31 | name: Closer 32 | icon: https://raw.githubusercontent.com/vauntdev/example/main/.vaunt/closer.png 33 | description: Only closers get coffee! 34 | triggers: 35 | - trigger: 36 | actor: author 37 | action: issue 38 | condition: closed = true 39 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | jesudunsinfaiye@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Rules 2 | 3 | - Don't create a pull request on an issue that doesn't exist, create an issue first and if the changes you are proposing are said to be okay, you can go ahead and create a pull request 4 | 5 | - Don't work on anything unless you are assigned, if you make a pull request without being assigned to that issue, it will be closed without being merged 6 | 7 | - Don't work on more than one issue at a time, this is so that you don't make a huge pull request and others can have opportunities to work on another issue while you work on something else 8 | 9 | - Do read the `readme.md` file 10 | 11 | - Add the Issue you worked on in your Pull Request 12 | 13 | - Don't work on the main branch, create your own branch by following the instructions [here](https://github.com/Dun-sin/Code-Magic/blob/main/CONTRIBUTING.md#-how-to-make-a-pull-request) 14 | 15 | - Don't commit the lock files eg package.json 16 | 17 | - Fill out issue and pull request(PR) templates properly, if you don't know how, check out previous issues/PR to know how they are filled or this video👇🏾 18 | 19 | #### 👌🏾 How to fill a pull request template 20 | [pull request template.webm](https://user-images.githubusercontent.com/78784850/195570788-05a6fe61-a9a3-4abe-ae17-936ffd6ea171.webm) 21 | 22 | > # Note: Breaking any of the rules above👆🏽 will get your PR rejected 23 | 24 | ## 👩🏽‍💻 Prerequisite Skills to Contribute 25 | 26 | ### Contribute in project 27 | 28 | - [Node.js](https://nodejs.org/) 29 | - [TypeScript](https://www.typescriptlang.org/) 30 | 31 | --- 32 | 33 | ## 💥 How to Contribute 34 | 35 | - Take a look at the existing [Issues](https://github.com/Dun-sin/Code-Magic/issues) or [create a new issue](https://github.com/Dun-sin/Code-Magic/issues/new/choose)! 36 | - [Fork the Repo](https://github.com/Dun-sin/Code-Magic/fork). Then, create a branch for any issue that you are working on. Finally, commit your work. 37 | - Create a [Pull Request](https://github.com/Dun-sin/Code-Magic/compare) (PR), which will be promptly reviewed and given suggestions for improvements by the community. 38 | - Add screenshots or screen captures to your Pull Request to help us understand the effects of the changes proposed in your PR. 39 | 40 | --- 41 | 42 | ## 🌟 HOW TO MAKE A PULL REQUEST: 43 | 44 | 1. Start by making a Fork of the [Code-Magic](https://github.com/Dun-sin/Code-Magic) repository. Click on the Fork symbol at the top right corner. 45 | 46 | 2. Clone your new fork of the repository in the terminal/CLI on your computer with the following command: 47 | 48 | ```bash 49 | git clone https://github.com//Code-Magic 50 | ``` 51 | 52 | 3. Navigate to the newly created Code-Magic project directory: 53 | 54 | ```bash 55 | cd Code-Magic 56 | ``` 57 | 58 | 4. Set upstream command: 59 | 60 | ```bash 61 | git remote add upstream https://github.com/Dun-sin/Code-Magic.git 62 | ``` 63 | 64 | 5. Create a new branch: 65 | 66 | ```bash 67 | git checkout -b YourBranchName 68 | ``` 69 | 70 | 6. Sync your fork or your local repository with the origin repository: 71 | 72 | - In your forked repository, click on "Fetch upstream" 73 | - Click "Fetch and merge" 74 | 75 | ### Alternatively, Git CLI way to Sync forked repository with origin repository: 76 | 77 | ```bash 78 | git fetch upstream 79 | ``` 80 | 81 | ```bash 82 | git merge upstream/main 83 | ``` 84 | 85 | ### [Github Docs](https://docs.github.com/en/github/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-on-github) for Syncing 86 | 87 | 7. Make your changes to the source code. 88 | 89 | 8. Stage your changes and commit: 90 | 91 | ⚠️ Make sure not to commit package.json or package-lock.json file 92 | 93 | ```bash 94 | git cz 95 | ``` 96 | 97 | 9. Push your local commits to the remote repository: 98 | 99 | ```bash 100 | git push origin YourBranchName 101 | ``` 102 | 103 | 10. Create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)! 104 | 105 | 11. Congratulations! You've made your first contribution to [Code-Magic](https://github.com/Dun-sin/Code-Magic/graphs/contributors)! 106 | 107 | 🏆 After this, the maintainers will review the PR and will merge it if it helps move the Code-Magic project forward. Otherwise, it will be given constructive feedback and suggestions for the changes needed to add the PR to the codebase. 108 | 109 | --- 110 | 111 | ## 💥 Issues 112 | 113 | In order to discuss changes, you are welcome to [open an issue](https://github.com/Dun-sin/Code-Magic/issues/new/choose) about what you would like to contribute. Enhancements are always encouraged and appreciated. 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dunsin 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 | 9 |
10 | 11 | 12 | ### 🚀 About 13 | 14 | Code Magic is a website where you can get simple CSS tricks to make your application elegant with just few clicks. 15 | 16 | ![code magic v2](https://github-production-user-asset-6210df.s3.amazonaws.com/78784850/268496391-eb41fa2f-0617-49e1-9035-c9ade068624d.gif) 17 | 18 | ### 🧙‍♀️ Magic options available for you: 19 | 20 | - Pic Text - Convert images to CSS art. Just upload and get CSS art with just one click. 21 | - Gradient Text - Dual color gradient for your texts. 22 | - Gradient Border - Get dual color gradient with desired radius. 23 | - Gradient Background - Make your backgrounds stand out by applying gradient colors. 24 | - Animations 25 | - Select one of the below options 26 | 27 | 28 | 29 | 39 | 40 |
30 |

Skew

31 |
32 | 33 |

Fade

34 |
35 | 36 |

Spin

37 |
38 |
41 | - Set duration and speed for the animations 42 | - And boom 43 | - Box Shadow - Cast a shadow on your element. 44 | - Border Radius - Get desired border radius for your element. 45 | - Text Shadow - Create a cool shadow for your text. 46 | - Input Range - Style your range input type elements easily and quickly 47 | 48 | ### ⚒️ Languages / Tools 49 | 50 | 51 | 52 | 53 | 57 | 58 | 62 | 66 | 67 | 68 |
54 | HTML 55 |
HTML 56 |
59 | CSS 60 |
CSS 61 |
63 | Typescript 64 |
TypeScript 65 |
69 | 70 |
71 | 72 | ## 🧑🏾‍💻 Demo 73 | 74 | Check out the website: [Code Magic](https://Code-Magic.vercel.app/) 75 | 76 | ## 👇🏽 Prerequisites 77 | 78 | Before installation, please make sure you have already installed the following tools: 79 | 80 | - [Git](https://git-scm.com/downloads) 81 | - [NodeJs](https://nodejs.org/en/download/) 82 | 83 | ## 👌🏾 What you have to do to contribute 84 | 85 | - [Read the rules](https://github.com/Dun-sin/Code-Magic/blob/main/CONTRIBUTING.md#rules) 86 | - [Follow the installation Steps](#%EF%B8%8F-installation-steps) 87 | - [Follow the contributing Steps](#-after-making-a-change) 88 | 89 | ## 🛠️ Installation Steps 90 | 91 | 1. [Fork](https://github.com/Dun-sin/Code-Magic/fork) the project. Click on the icon in the top right to get started 92 | 2. Clone the project, you can use the following command: 93 | 94 | ```bash 95 | git clone https://github.com//Code-Magic 96 | ``` 97 | 98 | 3. Navigate to the project directory 99 | 100 | ```bash 101 | cd Code-Magic 102 | ``` 103 | 104 | 4. Install dependencies with npm install 105 | 106 | ```bash 107 | npm install 108 | ``` 109 | 110 | ```bash 111 | npm install -g commitizen 112 | ``` 113 | 114 | 5. Run the project 115 | 116 | ```bash 117 | npm run dev 118 | ``` 119 | 120 | ## 🥂 After making a change 121 | 122 | 1. Create a new branch 123 | 124 | ```bash 125 | git checkout -b YourBranchName 126 | ``` 127 | 128 | 2. Add it to staging area 129 | 130 | > NOTE: don't commit the package.json 131 | 132 | ```bash 133 | git add 134 | ``` 135 | 136 | 3. Commit your changes with 137 | 138 | ```bash 139 | git cz 140 | ``` 141 | 142 | 4. Push your changes 143 | 144 | ```bash 145 | git push 146 | ``` 147 | 148 | ## 👨‍👩‍👦 Community 149 | 150 | Don't forget to join the discord community - [Join us](https://discord.com/invite/ufcysW9q23) 151 | 152 | ## 👩🏽‍💻 Contributing 153 | 154 | - Contributions make the open source community such an amazing place to learn, inspire, and create. 155 | - Any contributions you make are greatly appreciated. 156 | - Check out our [contribution guidelines](/CONTRIBUTING.md) for more information. 157 | 158 | ## 🛡️ License 159 | 160 | Code-Magic is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 161 | 162 | ## 💪🏽 Thanks to all Contributors 163 | 164 | Thanks a lot for spending your time helping Code-Magic grow. Thanks a lot! Keep rocking🍻 165 | 166 | [![Contributors](https://contrib.rocks/image?repo=Dun-sin/Code-Magic)](https://github.com/Dun-sin/Code-Magic/graphs/contributors) 167 | 168 | ## 🙏🏽 Support 169 | 170 | This project needs a star️ from you. Don't forget to leave a star✨ 171 | -------------------------------------------------------------------------------- /assets/bi_arrow-right-square-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/close-square-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dun-sin/Code-Magic/1e508c03f3c0f81bd0f7e64f8c48ef12e9e034f7/assets/favicon.ico -------------------------------------------------------------------------------- /assets/open-graph.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dun-sin/Code-Magic/1e508c03f3c0f81bd0f7e64f8c48ef12e9e034f7/assets/open-graph.webp -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Code-Magic", 3 | "private": true, 4 | "version": "0.0.0-development", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview", 9 | "test": "vitest", 10 | "test:ui": "vitest --ui", 11 | "test:run": "vitest run", 12 | "semantic-release": "semantic-release --branches main", 13 | "lint": "eslint --ext .js,.ts .", 14 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", 15 | "prepare": "husky install" 16 | }, 17 | "devDependencies": { 18 | "@types/dom-to-image": "^2.6.4", 19 | "@types/jest": "^29.5.12", 20 | "@types/jsdom": "^21.1.7", 21 | "@typescript-eslint/eslint-plugin": "^5.33.0", 22 | "@typescript-eslint/parser": "^5.33.0", 23 | "@vitest/ui": "^0.28.5", 24 | "cz-conventional-changelog": "^3.3.0", 25 | "eslint": "^8.21.0", 26 | "eslint-config-prettier": "^8.5.0", 27 | "eslint-config-standard": "^17.0.0", 28 | "eslint-plugin-import": "^2.26.0", 29 | "eslint-plugin-n": "^15.2.4", 30 | "eslint-plugin-promise": "^6.0.0", 31 | "husky": "^8.0.0", 32 | "jsdom": "^24.1.0", 33 | "prettier": "2.7.1", 34 | "pretty-quick": "^3.1.3", 35 | "semantic-release": "^19.0.3", 36 | "typedoc": "^0.23.15", 37 | "typescript": "^4.5.4", 38 | "vite": "^2.9.9", 39 | "vitest": "^0.28.5" 40 | }, 41 | "dependencies": { 42 | "@s-r0/eggy-js": "^1.3.4", 43 | "copy-to-clipboard": "^3.3.1", 44 | "dom-to-image": "^2.6.0", 45 | "filepond": "^4.30.4", 46 | "filepond-plugin-image-preview": "^4.6.11", 47 | "filepond-plugin-image-resize": "^2.0.10", 48 | "filepond-plugin-image-transform": "^3.8.7", 49 | "happy-dom": "^8.3.2", 50 | "vite-tsconfig-paths": "^4.3.2" 51 | }, 52 | "config": { 53 | "commitizen": { 54 | "path": "./node_modules/cz-conventional-changelog" 55 | } 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "https://github.com/Dun-sin/Code-Magic.git" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/eggy-js.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@s-r0/eggy-js'; 2 | -------------------------------------------------------------------------------- /src/lib/getElements.ts: -------------------------------------------------------------------------------- 1 | export const getGeneratorsElement = (): NodeListOf => 2 | document.querySelectorAll('[data-gen]') as NodeListOf; 3 | 4 | export const getOpenSideBarButton = (): HTMLElement => 5 | document.querySelector('.open-sidebar') as HTMLElement; 6 | 7 | export const getResultPage = (): HTMLElement => 8 | document.querySelector('.side-results') as HTMLElement; 9 | 10 | export const getCssOrTailwindButton = (attribute: string): HTMLElement => 11 | document.querySelector( 12 | `[data-css-tailwind=${attribute}-code]` 13 | ) as HTMLElement; 14 | 15 | export const getCssOrTailwindDropdown = (attribute: string): HTMLElement => 16 | document.querySelector( 17 | `[data-dropdown=${attribute}-dropdown2]` 18 | ) as HTMLElement; 19 | 20 | export const getPngOrSvgButton = (attribute: string): HTMLElement => 21 | document.querySelector(`[data-png-svg=${attribute}-image]`) as HTMLElement; 22 | 23 | export const getPngOrSvgDropdown = (attribute: string): HTMLElement => 24 | document.querySelector( 25 | `[data-dropdown=${attribute}-dropdown1]` 26 | ) as HTMLElement; 27 | 28 | export const getCopyCodeButton = (attribute: string): HTMLElement => 29 | document.querySelector(`[data-download=${attribute}-code]`) as HTMLElement; 30 | 31 | export const getTailwindButton = (attribute: string): HTMLElement => 32 | document.querySelector( 33 | `[data-download=${attribute}-tailwind]` 34 | ) as HTMLElement; 35 | 36 | export const getPNGButton = (attribute: string): HTMLElement => 37 | document.querySelector(`[data-download=${attribute}-PNG]`) as HTMLElement; 38 | 39 | export const getSVGButton = (attribute: string): HTMLElement => 40 | document.querySelector(`[data-download=${attribute}-svg]`) as HTMLElement; 41 | 42 | export const getResultButton = (attribute: string): HTMLElement => 43 | document.querySelector(`[data-button = ${attribute}]`) as HTMLElement; 44 | 45 | export const getColorInput1 = (attribute: string): HTMLInputElement => 46 | document.getElementById(`${attribute}-color1`) as HTMLInputElement; 47 | 48 | export const getColorInput2 = (attribute: string): HTMLInputElement => 49 | document.getElementById(`${attribute}-color2`) as HTMLInputElement; 50 | 51 | export const getAllColorInput = ( 52 | attribute: string 53 | ): NodeListOf => 54 | document.querySelectorAll(`[data-content=${attribute}] .color input`); 55 | 56 | export const getAllInputElements = ( 57 | attribute: string 58 | ): NodeListOf => 59 | document.querySelectorAll(`.${attribute}-inputs`); 60 | 61 | export const getGradientPreview = (attribute: string): HTMLElement => 62 | document.querySelector(`#${attribute}-color-preview`) as HTMLElement; 63 | 64 | export const getOutput = (attribute: string): HTMLElement => 65 | document.querySelector( 66 | `[data-result = ${attribute}] > .output` 67 | ) as HTMLElement; 68 | 69 | export const getRange = (attribute: string): HTMLInputElement => 70 | document.getElementById(`${attribute}-degree`) as HTMLInputElement; 71 | 72 | export const getInputText = (attribute: string) => 73 | document.getElementById(`${attribute}-text`) as HTMLInputElement; 74 | 75 | export const getCheckbox = (attribute: string): HTMLInputElement => 76 | document.getElementById(`${attribute}-radius`) as HTMLInputElement; 77 | 78 | export const getRadiusInput = (attribute: string) => 79 | document.getElementById(`${attribute}-input`) as HTMLInputElement; 80 | 81 | export const getInputSpinner = (attribute: string) => 82 | document.getElementById(`${attribute}-duration`) as HTMLInputElement; 83 | 84 | export const getRadioButtonSet = (attribute: string) => 85 | document.querySelectorAll( 86 | `[name = ${attribute}-radio]` 87 | ) as NodeListOf; 88 | 89 | export const getBorderTop = (attribute: string) => 90 | document.getElementById(`${attribute}-top`) as HTMLInputElement; 91 | 92 | export const getBorderRight = (attribute: string) => 93 | document.getElementById(`${attribute}-right`) as HTMLInputElement; 94 | 95 | export const getBorderBottom = (attribute: string) => 96 | document.getElementById(`${attribute}-bottom`) as HTMLInputElement; 97 | 98 | export const getBorderLeft = (attribute: string) => 99 | document.getElementById(`${attribute}-left`) as HTMLInputElement; 100 | 101 | export const getStyleSheet = () => { 102 | const stylesheet = Array.from(document.styleSheets).filter( 103 | (styleSheet) => 104 | !styleSheet.href || styleSheet.href.startsWith(location.origin) 105 | ); 106 | return stylesheet[0] as CSSStyleSheet; 107 | }; 108 | 109 | export const getShadowHorizontalOffset = ( 110 | attribute: string 111 | ): HTMLInputElement => 112 | document.getElementById(`${attribute}-h-offset`) as HTMLInputElement; 113 | 114 | export const getShadowVerticalOffset = (attribute: string): HTMLInputElement => 115 | document.getElementById(`${attribute}-v-offset`) as HTMLInputElement; 116 | 117 | export const getShadowBlur = (attribute: string): HTMLInputElement => 118 | document.getElementById(`${attribute}-blur`) as HTMLInputElement; 119 | 120 | export const getShadowSpread = (attribute: string): HTMLInputElement => 121 | document.getElementById(`${attribute}-spread`) as HTMLInputElement; 122 | 123 | export const getShadowColor = (attribute: string): HTMLInputElement => 124 | document.getElementById(`${attribute}-color`) as HTMLInputElement; 125 | 126 | export const getShadowPreview = (attribute: string): HTMLInputElement => 127 | document.getElementById(`${attribute}-preview`) as HTMLInputElement; 128 | 129 | export const getParentElementOfColors = (attribute: string): HTMLElement => 130 | document.querySelector(`[data-content=${attribute}] .colors`) as HTMLElement; 131 | 132 | export const getNewColorButton = (attribute: string): HTMLElement => 133 | document.querySelector( 134 | `[data-content=${attribute}] .addNewColor` 135 | ) as HTMLElement; 136 | 137 | export const getRemoveNewColorButton = (attribute: string): HTMLElement => 138 | document.querySelector( 139 | `[data-content=${attribute}] .removeNewColor` 140 | ) as HTMLElement; 141 | 142 | export const getShadowFields = ( 143 | attribute: string, 144 | types: string[] 145 | ): HTMLSpanElement[] => 146 | types.reduce( 147 | (acc, type) => [ 148 | ...acc, 149 | document.getElementById(`${attribute}-${type}-field`) as HTMLInputElement, 150 | ], 151 | [] 152 | ); 153 | 154 | export const getPreviewSlider = (attribute: string): HTMLElement => 155 | document.querySelector( 156 | `[data-content=${attribute}] .preview-slider` 157 | ) as HTMLElement; 158 | 159 | export const getAllFields = (attribute: string) => { 160 | const inputs = document.querySelectorAll( 161 | `[data-content=${attribute}] input` 162 | ) as NodeListOf; 163 | const textarea = document.querySelector( 164 | `[data-content='${attribute}'] textarea` 165 | ) as HTMLTextAreaElement; 166 | 167 | return { 168 | inputs, 169 | textarea, 170 | }; 171 | }; 172 | 173 | export const getResetButton = (attribute: string) => 174 | document.querySelector(`[data-reset=${attribute}]`) as HTMLButtonElement; 175 | 176 | export const getDegreeSpanElement = (attribute: string) => 177 | document.querySelector( 178 | `[data-content=${attribute}] .unit-display` 179 | ) as HTMLSpanElement; 180 | 181 | export const getNumberOfColumns = (attribute: string): HTMLInputElement => 182 | document.getElementById(`${attribute}-columns`) as HTMLInputElement; 183 | 184 | export const getNumberOfRows = (attribute: string): HTMLInputElement => 185 | document.getElementById(`${attribute}-rows`) as HTMLInputElement; 186 | 187 | export const getRowGap = (attribute: string): HTMLInputElement => 188 | document.getElementById(`${attribute}-row-gaps`) as HTMLInputElement; 189 | 190 | export const getColumnGap = (attribute: string): HTMLInputElement => 191 | document.getElementById(`${attribute}-column-gaps`) as HTMLInputElement; 192 | 193 | export const getGridFields = ( 194 | attribute: string, 195 | types: string[] 196 | ): HTMLSpanElement[] => 197 | types.reduce( 198 | (acc, type) => [ 199 | ...acc, 200 | document.getElementById(`${attribute}-${type}`) as HTMLInputElement, 201 | ], 202 | [] 203 | ); 204 | 205 | export const getGridPreview = (attribute: string): HTMLElement => 206 | document.getElementById(`${attribute}-preview`) as HTMLElement; 207 | 208 | export const getScrollPreview = (): HTMLElement => 209 | document.getElementById('scroll-preview') as HTMLElement; 210 | -------------------------------------------------------------------------------- /src/lib/packages/helpers.ts: -------------------------------------------------------------------------------- 1 | // Includes all functions that the utils functions use 2 | 3 | import { 4 | getColumnGap, 5 | getNumberOfColumns, 6 | getNumberOfRows, 7 | getRowGap, 8 | } from '../getElements'; 9 | 10 | import {Eggy} from '@s-r0/eggy-js'; 11 | import copy from 'copy-to-clipboard'; 12 | 13 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 14 | // @ts-ignore 15 | 16 | export function createDownloadLink(fileName: string, url: string) { 17 | const link = document.createElement('a'); 18 | link.download = fileName; 19 | link.href = url; 20 | return link; 21 | } 22 | 23 | /** 24 | * what should copy when the copy css button is clicked 25 | * 26 | * @param attribute attribute of the clicked generator 27 | * @param outputElement output element to display result 28 | */ 29 | export const actOnGenerator = ( 30 | attribute: string, 31 | outputElement: HTMLElement 32 | ) => { 33 | let codeToCopy = ''; 34 | let element; 35 | 36 | switch (attribute) { 37 | case 'pic-text': 38 | codeToCopy = ` 39 | div { 40 | background-position: ${outputElement.style.backgroundPosition}; 41 | background-size: ${outputElement.style.backgroundSize}; 42 | background-repeat: ${outputElement.style.backgroundRepeat}; 43 | background-clip: ${outputElement.style.backgroundClip}; 44 | -webkit-background-clip: ${outputElement.style.webkitBackgroundClip}; 45 | -webkit-text-fill-color: ${outputElement.style.webkitTextFillColor}; 46 | } 47 | `; 48 | break; 49 | case 'gradient-text': 50 | codeToCopy = ` 51 | p{ 52 | font-size: ${(outputElement.children[0] as HTMLElement).style.fontSize}; 53 | background: ${ 54 | (outputElement.children[0] as HTMLElement).style.background 55 | }; 56 | background-clip: text; 57 | -webkit-background-clip: text; 58 | -webkit-text-fill-color: transparent; 59 | } 60 | `; 61 | 62 | break; 63 | case 'gradient-border': 64 | element = outputElement.style; 65 | const content = window.getComputedStyle(outputElement, '::before'); 66 | 67 | codeToCopy = ` 68 | div { 69 | position: relative; 70 | } 71 | 72 | div::before { 73 | content: ''; 74 | position: absolute; 75 | inset: 0; 76 | padding: 6px; 77 | -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); 78 | mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); 79 | -webkit-mask-composite: xor; 80 | mask-composite: exclude; 81 | background: ${content.background}; 82 | background-clip: 'border-box'; 83 | ${ 84 | content.borderRadius !== '0px' 85 | ? `border-radius: ${content.borderRadius};` 86 | : '' 87 | } 88 | } 89 | `; 90 | break; 91 | case 'gradient-background': 92 | element = outputElement.style; 93 | codeToCopy = ` 94 | div { 95 | height: 100px; 96 | width: 100px; 97 | background: ${element.backgroundImage}; 98 | } 99 | `; 100 | break; 101 | case 'border-radius': 102 | element = outputElement.style; 103 | codeToCopy = ` 104 | border-radius: ${element.borderRadius}; 105 | `; 106 | break; 107 | case 'box-shadow': 108 | element = outputElement.style; 109 | codeToCopy = ` 110 | div { 111 | height: 300px; 112 | width: 300px; 113 | box-shadow: ${element.boxShadow}; 114 | } 115 | `; 116 | break; 117 | case 'text-shadow': 118 | element = outputElement.style; 119 | codeToCopy = ` 120 | div { 121 | text-shadow: ${element.textShadow}; 122 | } 123 | `; 124 | break; 125 | case 'input-range': 126 | element = outputElement.style; 127 | codeToCopy = ` 128 | input[type='range'] { 129 | -webkit-appearance: none; 130 | appearance: none; 131 | background: transparent; 132 | cursor: pointer; 133 | width: ${element.getPropertyValue('--preview-track-width')}; 134 | } 135 | 136 | input[type='range']::-webkit-slider-runnable-track { 137 | background-color: ${element.getPropertyValue('--preview-track-color')}; 138 | height: ${element.getPropertyValue('--preview-track-height')}; 139 | width: 100%; 140 | border-radius: ${element.getPropertyValue('--preview-track-radius')}; 141 | } 142 | 143 | input[type='range']::-moz-range-track { 144 | background-color: ${element.getPropertyValue('--preview-track-color')}; 145 | height: ${element.getPropertyValue('--preview-track-height')}; 146 | width: 100%; 147 | border-radius: ${element.getPropertyValue('--preview-track-radius')}; 148 | } 149 | 150 | input[type='range']::-webkit-slider-thumb { 151 | -webkit-appearance: none; 152 | appearance: none; 153 | background-color: ${element.getPropertyValue('--preview-thumb-color')}; 154 | height: ${element.getPropertyValue('--preview-thumb-height')}; 155 | width: ${element.getPropertyValue('--preview-thumb-width')}; 156 | margin-top: -3.2px; 157 | border-radius: ${element.getPropertyValue('--preview-thumb-radius')}; 158 | } 159 | 160 | input[type='range']::-moz-range-thumb { 161 | border: none; 162 | background-color: ${element.getPropertyValue('--preview-thumb-color')}; 163 | height: ${element.getPropertyValue('--preview-thumb-height')}; 164 | width: ${element.getPropertyValue('--preview-thumb-width')}; 165 | border-radius: ${element.getPropertyValue('--preview-thumb-radius')}; 166 | } 167 | 168 | input[type='range']:focus { 169 | outline: none; 170 | } 171 | `; 172 | break; 173 | case 'grid-generators': 174 | element = outputElement.style; 175 | codeToCopy = ` 176 | div { 177 | height: 300px; 178 | width: 300px; 179 | display:${element.display}, 180 | grid-template-rows:${element.gridTemplateRows}, 181 | grid-template-columns:${element.gridTemplateColumns}, 182 | row-gap:${element.rowGap}, 183 | column-gap:${element.columnGap} 184 | } 185 | `; 186 | break; 187 | default: 188 | codeToCopy = ` 189 | Couldn't copy, please try again :( 190 | `; 191 | } 192 | 193 | try { 194 | copy(codeToCopy); 195 | } catch { 196 | Eggy({ 197 | title: `Whoops`, 198 | message: `Can't copy, try again`, 199 | type: 'error', 200 | }); 201 | } 202 | }; 203 | 204 | export function getQueryParam(param: string): string | null { 205 | const urlParams = new URLSearchParams(window.location.search); 206 | return urlParams.get(param); 207 | } 208 | 209 | export function setQueryParam(param: string, value: string): void { 210 | const url = new URL(window.location.toString()); 211 | const urlParams = url.searchParams; 212 | urlParams.set(param, value); 213 | window.history.replaceState({}, '', url.toString()); 214 | } 215 | 216 | export function deleteQueryParam(param: string): void { 217 | const url = new URL(window.location.toString()); 218 | const urlParams = url.searchParams; 219 | urlParams.delete(param); 220 | window.history.replaceState({}, '', url.toString()); 221 | } 222 | 223 | function convertLinearGradientToTailwind(gradient: string): string { 224 | const angle = extractDegreeFromGradient(gradient); 225 | const direction = getTailwindDirectionClass(angle); 226 | const rbgColors = extractRGBColorsFromGradient(gradient); 227 | const gradientClass = generateTailwindClasses(rbgColors); 228 | 229 | return `bg-gradient-${direction} ${gradientClass}`; 230 | 231 | function extractDegreeFromGradient(gradient: string): number { 232 | const regex = /linear-gradient\((\d+)deg/; 233 | const match = gradient.match(regex); 234 | 235 | if (match) { 236 | const degree = parseInt(match[1]); 237 | return degree; 238 | } 239 | 240 | return 0; 241 | } 242 | 243 | function getTailwindDirectionClass(angle: number) { 244 | const gradientDirections = [ 245 | {angle: 0, class: 'to-r'}, 246 | {angle: 45, class: 'to-tr'}, 247 | {angle: 90, class: 'to-t'}, 248 | {angle: 135, class: 'to-tl'}, 249 | {angle: 180, class: 'to-l'}, 250 | {angle: 225, class: 'to-bl'}, 251 | {angle: 270, class: 'to-b'}, 252 | {angle: 315, class: 'to-br'}, 253 | ]; 254 | 255 | const closest = gradientDirections.reduce((prev, curr) => { 256 | return Math.abs(curr.angle - angle) < Math.abs(prev.angle - angle) 257 | ? curr 258 | : prev; 259 | }); 260 | return closest.class; 261 | } 262 | 263 | function rgbToHex(rgb: string): string { 264 | const regex = /rgb\((\d+), (\d+), (\d+)\)/; 265 | const match = rgb.match(regex); 266 | 267 | if (match) { 268 | const r = parseInt(match[1]).toString(16).padStart(2, '0'); 269 | const g = parseInt(match[2]).toString(16).padStart(2, '0'); 270 | const b = parseInt(match[3]).toString(16).padStart(2, '0'); 271 | return `#${r}${g}${b}`; 272 | } 273 | 274 | return ''; 275 | } 276 | 277 | function extractRGBColorsFromGradient(gradient: string): string[] { 278 | const regex = /rgb\(\d+, \d+, \d+\)/g; 279 | const matches = gradient.match(regex); 280 | 281 | if (matches) { 282 | return matches.map((rgb) => rgbToHex(rgb)); 283 | } 284 | 285 | return []; 286 | } 287 | 288 | function generateTailwindClasses(colors: string[]): string { 289 | if (colors.length === 2) { 290 | return `from-[${colors[0]}] to-[${colors[1]}]`; 291 | } else if (colors.length === 3) { 292 | return `from-[${colors[0]}] via-[${colors[1]}] to-[${colors[2]}]`; 293 | } else if (colors.length === 4) { 294 | return `from-[${colors[0]}] via-[${colors[1]}] via-[${colors[2]}] to-[${colors[3]}]`; 295 | } 296 | 297 | return ''; 298 | } 299 | } 300 | 301 | function convertInputRangeStylesToTailwind(element: CSSStyleDeclaration) { 302 | const tailwindClasses = []; 303 | 304 | tailwindClasses.push(`appearance-none`); 305 | 306 | // Track height 307 | if (element.getPropertyValue('--preview-track-height')) { 308 | const trackHeight = element.getPropertyValue('--preview-track-height'); 309 | 310 | tailwindClasses.push( 311 | `[&::-webkit-slider-runnable-track]:h-[${trackHeight}] h-[${trackHeight}]` 312 | ); 313 | } 314 | 315 | // Track width 316 | if (element.getPropertyValue('--preview-track-width')) { 317 | const trackWidth = element.getPropertyValue('--preview-track-width'); 318 | 319 | tailwindClasses.push( 320 | `[&::-webkit-slider-runnable-track]:w-[${trackWidth}] w-[${trackWidth}]` 321 | ); 322 | } 323 | 324 | // Track radius 325 | if (element.getPropertyValue('--preview-track-radius')) { 326 | const trackRadius = element.getPropertyValue('--preview-track-radius'); 327 | 328 | tailwindClasses.push( 329 | `[&::-webkit-slider-runnable-track]:rounded-[${trackRadius}] rounded-[${trackRadius}]` 330 | ); 331 | } 332 | 333 | // Thumb height 334 | if (element.getPropertyValue('--preview-thumb-height')) { 335 | const thumbHeight = element.getPropertyValue('--preview-thumb-height'); 336 | 337 | tailwindClasses.push( 338 | `[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:mt-[-3.2px] [&::-webkit-slider-thumb]:h-[${thumbHeight}] [&::-moz-range-thumb]:h-[${thumbHeight}]` 339 | ); 340 | } 341 | 342 | // Thumb width 343 | if (element.getPropertyValue('--preview-thumb-width')) { 344 | const thumbWidth = element.getPropertyValue('--preview-thumb-width'); 345 | 346 | tailwindClasses.push( 347 | `[&::-webkit-slider-thumb]:w-[${thumbWidth}] [&::-moz-range-thumb]:w-[${thumbWidth}]` 348 | ); 349 | } 350 | 351 | // Thumb radius 352 | if (element.getPropertyValue('--preview-thumb-radius')) { 353 | const thumbRadius = element.getPropertyValue('--preview-thumb-radius'); 354 | 355 | tailwindClasses.push( 356 | `[&::-webkit-slider-thumb]:rounded-[${thumbRadius}] [&::-moz-range-thumb]:rounded-[${thumbRadius}]` 357 | ); 358 | } 359 | 360 | // Thumb color 361 | if (element.getPropertyValue('--preview-thumb-color')) { 362 | const thumbColor = element.getPropertyValue('--preview-thumb-color'); 363 | 364 | tailwindClasses.push( 365 | `[&::-webkit-slider-thumb]:bg-[${thumbColor}] [&::-moz-range-thumb]:bg-[${thumbColor}]` 366 | ); 367 | } 368 | 369 | // Track color 370 | if (element.getPropertyValue('--preview-track-color')) { 371 | const trackColor = element.getPropertyValue('--preview-track-color'); 372 | 373 | tailwindClasses.push( 374 | `[&::-webkit-slider-runnable-track]:bg-[${trackColor}] bg-[${trackColor}]` 375 | ); 376 | } 377 | 378 | return tailwindClasses.join(' '); 379 | } 380 | 381 | function convertGridSylesToTailwind(attribute: string) { 382 | let result = 'grid'; 383 | const noOfColumns = getNumberOfColumns(attribute).value; 384 | const noOfRows = getNumberOfRows(attribute).value; 385 | const columnGapValue = getColumnGap(attribute).value; 386 | const rowGapValue = getRowGap(attribute).value; 387 | const rows = parseInt(noOfRows !== '' ? noOfRows : '0'); 388 | const columns = parseInt(noOfColumns !== '' ? noOfColumns : '0'); 389 | const rowGap = parseInt(rowGapValue !== '' ? rowGapValue : '0'); 390 | const columnGap = parseInt(columnGapValue !== '' ? columnGapValue : '0'); 391 | if (rows > 0 || columns > 0 || rowGap > 0 || columnGap > 0) { 392 | result = `${result} grid-rows-${rows} grid-cols-${columns} gap-x-${rowGap} gap-y-${columnGap}`; 393 | } else { 394 | result = `${result} gap-x-0 gap-y-0`; 395 | } 396 | return result; 397 | } 398 | 399 | /** 400 | * what should copy when the copy Tailwind button is clicked 401 | * 402 | * @param attribute attribute of the clicked generator 403 | * @param outputElement output element to display result 404 | */ 405 | export const actOnTailwindGenerator = ( 406 | attribute: string, 407 | outputElement: HTMLElement 408 | ) => { 409 | let element = outputElement.style; 410 | let codeToCopy = ''; 411 | switch (attribute) { 412 | case 'pic-text': 413 | codeToCopy = ``; 414 | break; 415 | case 'gradient-text': 416 | const output = outputElement.children[0] as HTMLElement; 417 | 418 | codeToCopy = `text-[${ 419 | output.style.fontSize 420 | }] ${convertLinearGradientToTailwind( 421 | output.style.backgroundImage 422 | )} text-transparent bg-clip-text`; 423 | break; 424 | case 'gradient-border': 425 | codeToCopy = ``; 426 | break; 427 | case 'gradient-background': 428 | codeToCopy = `${convertLinearGradientToTailwind( 429 | element.backgroundImage 430 | )}`; 431 | break; 432 | case 'border-radius': 433 | codeToCopy = `bg-[${element.borderRadius.replace(/ /g, '_')}]`; 434 | break; 435 | case 'box-shadow': 436 | codeToCopy = `shadow-[${element.boxShadow.replace(/ /g, '_')}]`; 437 | break; 438 | case 'text-shadow': 439 | codeToCopy = `[text-shadow:${element.textShadow.replace(/[\s,]/g, '_')}]`; 440 | break; 441 | case 'input-range': 442 | codeToCopy = `${convertInputRangeStylesToTailwind(element)}`; 443 | break; 444 | case 'grid-generators': 445 | codeToCopy = `${convertGridSylesToTailwind(attribute)}`; 446 | break; 447 | default: 448 | codeToCopy = ` 449 | Couldn't copy, please try again :( 450 | `; 451 | } 452 | 453 | try { 454 | copy(codeToCopy); 455 | } catch { 456 | Eggy({ 457 | title: `Whoops`, 458 | message: `Can't copy, try again`, 459 | type: 'error', 460 | }); 461 | } 462 | }; 463 | -------------------------------------------------------------------------------- /src/lib/packages/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | actOnGenerator, 3 | actOnTailwindGenerator, 4 | createDownloadLink, 5 | setQueryParam, 6 | } from './helpers'; 7 | import { 8 | getAllColorInput, 9 | getAllInputElements, 10 | getDegreeSpanElement, 11 | getGradientPreview, 12 | getNewColorButton, 13 | getParentElementOfColors, 14 | getRange, 15 | getRemoveNewColorButton, 16 | getResetButton, 17 | } from '../getElements'; 18 | 19 | import DomToImage from 'dom-to-image'; 20 | import {Eggy} from '@s-r0/eggy-js'; 21 | 22 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 23 | // @ts-ignore 24 | 25 | export const setGradientDegreeValue = (degreeElement: HTMLElement): void => 26 | degreeElement.addEventListener('input', (e) => { 27 | const target = e.target as HTMLInputElement; 28 | const unitDisplayElement = target.parentElement?.querySelector( 29 | '.unit-display' 30 | ) as HTMLElement; 31 | 32 | // change the unit for opacity 33 | const unit = unitDisplayElement.innerText.toLowerCase().includes('opacity') 34 | ? '' 35 | : 'deg'; 36 | unitDisplayElement.innerText = `${target.value}${unit}`; 37 | }); 38 | 39 | export function slideIn(slider: HTMLElement, isOpen: boolean) { 40 | if (isOpen) return; 41 | 42 | const slideIn = [{left: '-300px'}, {left: '-10px'}]; 43 | const slideInTiming = { 44 | duration: 500, 45 | iterations: 1, 46 | fill: 'both' as FillMode, 47 | }; 48 | 49 | slider.animate(slideIn, slideInTiming); 50 | } 51 | 52 | export const createGradientPreview = ( 53 | range: HTMLInputElement, 54 | attribute: string 55 | ) => { 56 | const fill = range?.value; 57 | getGradientPreview( 58 | attribute 59 | ).style.background = `linear-gradient(${fill}deg, ${getColorsValue( 60 | attribute 61 | ).join(', ')})`; 62 | 63 | setQueryParam( 64 | 'values', 65 | `linear-gradient(${fill}deg, ${getColorsValue(attribute).join(', ')})` 66 | ); 67 | }; 68 | 69 | /** 70 | * Allows you to copy CSS code to clipboard 71 | * 72 | * @param attribute The attribute name of the generator element 73 | * @param outputElement output element to display result 74 | */ 75 | export function copyCSSCodeToClipboard( 76 | attribute: string, 77 | outputElement: HTMLElement 78 | ): void { 79 | actOnGenerator(attribute, outputElement); 80 | } 81 | 82 | /** 83 | * Allows you to copy Tailwind code to clipboard 84 | * 85 | * @param attribute The attribute name of the generator element 86 | * @param outputElement output element to display result 87 | */ 88 | export function copyTailwindCodeToClipboard( 89 | attribute: string, 90 | outputElement?: HTMLElement | null 91 | ): void { 92 | if (outputElement) actOnTailwindGenerator(attribute, outputElement); 93 | else console.log("Can't generate tailwind code"); 94 | } 95 | 96 | export const addRule = (function (style) { 97 | const sheet = document.head.appendChild(style).sheet; 98 | return function (selector: string, css: {[x: string]: string}) { 99 | const propText = 100 | typeof css === 'string' 101 | ? css 102 | : Object.keys(css) 103 | .map(function (p) { 104 | return p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p]); 105 | }) 106 | .join(';'); 107 | sheet?.insertRule(selector + '{' + propText + '}', sheet.cssRules.length); 108 | }; 109 | })(document.createElement('style')); 110 | 111 | export function downloadPNG(attribute: string, outputImage: HTMLElement): void { 112 | DomToImage.toPng(outputImage, {quality: 1}).then((dataUrl) => { 113 | const link = createDownloadLink(`${attribute}.png`, dataUrl); 114 | link.click(); 115 | }); 116 | } 117 | 118 | export function downloadSVG(attribute: string, outputImage: HTMLElement): void { 119 | DomToImage.toSvg(outputImage).then((dataUrl) => { 120 | const link = createDownloadLink(`${attribute}.svg`, dataUrl); 121 | link.click(); 122 | }); 123 | } 124 | 125 | /** 126 | * Show the popup on a page 127 | * 128 | * @param title - Main Text 129 | * @param message - Secondary Text 130 | * @param type - To specify the type of a popup, Like: success, warning, info, error, 131 | */ 132 | 133 | export function showPopup(title: string, message: string, type: string): void { 134 | Eggy({ 135 | title, 136 | message, 137 | type, 138 | }); 139 | } 140 | 141 | export function triggerEmptyAnimation(inputElement: HTMLInputElement): void { 142 | inputElement.style.borderColor = 'red'; 143 | inputElement.animate( 144 | [ 145 | {transform: 'translate(10px, 0)'}, 146 | {transform: 'translate(-10px, 0)'}, 147 | {transform: 'translate(0, 0)'}, 148 | ], 149 | {duration: 300} 150 | ); 151 | 152 | setTimeout(() => { 153 | inputElement.style.borderColor = 'white'; 154 | }, 1000); 155 | } 156 | 157 | export function addNewColorPicker(attribute: string): void { 158 | const getParentElementToAddTo = getParentElementOfColors(attribute); 159 | const numberOfChildren = getParentElementToAddTo?.childElementCount; 160 | 161 | if (numberOfChildren === undefined || numberOfChildren === 4) return; 162 | 163 | const colorNumber = numberOfChildren + 1; 164 | 165 | getParentElementToAddTo?.appendChild(createLabelForNewColor()); 166 | whatColorButtonShouldShow(attribute); 167 | 168 | // create label element 169 | function createLabelForNewColor(): Node { 170 | const labelWrapperForColor = document.createElement('label'); 171 | labelWrapperForColor.setAttribute('for', 'color'); 172 | labelWrapperForColor.className = 'color'; 173 | 174 | labelWrapperForColor.appendChild(createNewColor()); 175 | 176 | return labelWrapperForColor; 177 | } 178 | 179 | // create color pickter 180 | function createNewColor(): Node { 181 | const newColorCreated = document.createElement('input'); 182 | newColorCreated.setAttribute('type', 'text'); 183 | newColorCreated.setAttribute('data-coloris', ''); 184 | newColorCreated.placeholder = 'Tap to pick a color'; 185 | newColorCreated.id = `${attribute}-color${colorNumber}`; 186 | newColorCreated.className = `${attribute}-inputs`; 187 | 188 | return newColorCreated; 189 | } 190 | } 191 | 192 | export function getColorsValue(attribute: string): Array { 193 | const colorValues: string[] = []; 194 | 195 | const colorInput = getAllColorInput(attribute); 196 | 197 | colorInput.forEach((value) => { 198 | const colorValue = value as HTMLInputElement; 199 | colorValues.push(colorValue.value); 200 | }); 201 | 202 | return colorValues; 203 | } 204 | 205 | export function setColorsValue(colors: string[] | null, attribute: string) { 206 | if (!colors) return; 207 | const colorInputs = getAllColorInput(attribute); 208 | 209 | colorInputs.forEach( 210 | (colorInput, index) => (colorInput.value = colors[index]) 211 | ); 212 | } 213 | 214 | export function removeColorPicker(attribute: string): void { 215 | const getParentElementToRemoveFrom = getParentElementOfColors(attribute); 216 | const numberOfChildren = getParentElementToRemoveFrom?.childElementCount; 217 | 218 | if (numberOfChildren === undefined || numberOfChildren === 2) return; 219 | getParentElementToRemoveFrom?.lastChild?.remove(); 220 | 221 | createGradientPreview(getRange(attribute), attribute); 222 | whatColorButtonShouldShow(attribute); 223 | } 224 | 225 | export const whatColorButtonShouldShow = (attribute: string): void => { 226 | const getNumberOfChildren = 227 | getParentElementOfColors(attribute).childElementCount; 228 | 229 | // display add new color button 230 | if (getNumberOfChildren === 2 || getNumberOfChildren !== 4) { 231 | getNewColorButton(attribute).style.display = 'flex'; 232 | } else { 233 | getNewColorButton(attribute).style.display = 'none'; 234 | } 235 | 236 | // display remove color button 237 | if (getNumberOfChildren > 2) { 238 | getRemoveNewColorButton(attribute).style.display = 'flex'; 239 | } else { 240 | getRemoveNewColorButton(attribute).style.display = 'none'; 241 | } 242 | }; 243 | 244 | // close dropdown on outside click 245 | export function closeDropdown( 246 | toggleFunction: () => void, 247 | dropdown: HTMLElement, 248 | classToToggle: string 249 | ): void { 250 | document.documentElement.addEventListener('click', function () { 251 | if (dropdown.classList.contains(classToToggle)) { 252 | toggleFunction(); 253 | } 254 | }); 255 | } 256 | 257 | export function inputEventListner(attribute: string) { 258 | const gradientInputs = getAllInputElements(attribute); 259 | 260 | gradientInputs.forEach((inputElement) => { 261 | inputElement.addEventListener('input', () => { 262 | createGradientPreview(getRange(attribute), attribute); 263 | }); 264 | }); 265 | } 266 | 267 | export function addEventListenerToTheNewColorPicker(attribute: string) { 268 | const resetButton = getResetButton(attribute); 269 | 270 | inputEventListner(attribute); 271 | if (resetButton.classList.contains('reset-show')) return; 272 | resetButton.classList.add('reset-show'); 273 | } 274 | 275 | export function applyGradientValues(values: string, attribute: string) { 276 | const colors = values.match(/#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b/g) as 277 | | string[] 278 | | null; 279 | const degreeValueReg = values.match(/linear-gradient\((\d+)deg/) as 280 | | string[] 281 | | null; 282 | 283 | if (degreeValueReg) { 284 | const degreeValue = degreeValueReg[1]; 285 | const degreeSpanElement = getDegreeSpanElement(attribute); 286 | const rangeElement = getRange(attribute); 287 | 288 | degreeSpanElement.innerHTML = `${degreeValue}deg`; 289 | rangeElement.value = degreeValue; 290 | } 291 | 292 | if (colors) { 293 | const colorLength = colors.length; 294 | 295 | if (colorLength > 2) { 296 | for (let index = 2; index < colorLength; index++) { 297 | addNewColorPicker(attribute); 298 | addEventListenerToTheNewColorPicker(attribute); 299 | } 300 | whatColorButtonShouldShow(attribute); 301 | } 302 | 303 | setColorsValue(colors, attribute); 304 | } 305 | 306 | createGradientPreview(getRange(attribute), attribute); 307 | } 308 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | // Generator Modules 2 | import { 3 | addAnimationListener, 4 | animationGenerator, 5 | applyAnimationValues, 6 | displayAnimationPreview, 7 | } from './pages/animation'; 8 | import { 9 | addBorderRadiusListener, 10 | applyBorderRadiusValues, 11 | borderRadiusGenerator, 12 | } from './pages/border-radius'; 13 | import { 14 | addBoxShadowListener, 15 | applyBoxShadowValues, 16 | boxShadowGenerator, 17 | } from './pages/box-shadow'; 18 | import { 19 | addGradientBackgroundListener, 20 | gradientBackgroundGenerator, 21 | } from './pages/gradient-background'; 22 | import { 23 | addGradientBorderListener, 24 | gradientBorderGenerator, 25 | } from './pages/gradient-border'; 26 | import { 27 | addGradientTextListener, 28 | gradientTextGenerator, 29 | } from './pages/gradient-text'; 30 | import {applyInputRangeValues, rangeGenerator} from './pages/input-range'; 31 | import {picTextGenerator} from './pages/pic-text'; 32 | import { 33 | addTextShadowListener, 34 | applyTextShadowValues, 35 | textShadowGenerator, 36 | } from './pages/text-shadow'; 37 | import {applyGridValues, gridGenerator} from './pages/grid-generator'; 38 | 39 | // Packages 40 | import * as FilePond from 'filepond'; 41 | import 'filepond/dist/filepond.min.css'; 42 | 43 | // File Pond Plugins 44 | import FilePondPluginImagePreview from 'filepond-plugin-image-preview'; 45 | import FilePondPluginImageResize from 'filepond-plugin-image-resize'; 46 | import FilePondPluginImageTransform from 'filepond-plugin-image-transform'; 47 | 48 | import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css'; 49 | import { 50 | getGeneratorsElement, 51 | getInputSpinner, 52 | getOpenSideBarButton, 53 | getRadioButtonSet, 54 | getRange, 55 | getResultPage, 56 | } from './lib/getElements'; 57 | import { 58 | addTransformListener, 59 | applyTransformValue, 60 | transformGenerator, 61 | } from './pages/transform'; 62 | import {scrollGenerator} from './pages/scroll'; 63 | import { 64 | deleteQueryParam, 65 | getQueryParam, 66 | setQueryParam, 67 | } from './lib/packages/helpers'; 68 | import {applyGradientValues} from './lib/packages/utils'; 69 | 70 | FilePond.registerPlugin( 71 | FilePondPluginImagePreview, 72 | FilePondPluginImageResize, 73 | FilePondPluginImageTransform 74 | ); 75 | 76 | /** 77 | * All types 78 | */ 79 | type Display = 'grid' | 'flex' | 'none'; 80 | type openResults = 'newResults' | 'oldResults' | null; 81 | 82 | /** 83 | * All Variables 84 | */ 85 | const FINAL_WIDTH = 300; 86 | let attributeValue: string | null = null; 87 | let imageSRC: string; 88 | let imageHeight: number; 89 | 90 | const sideBarSlide = [ 91 | {left: '30%', opacity: '0'}, 92 | {left: '0%', opacity: '1'}, 93 | ]; 94 | const sideBarSlideOut = [ 95 | {left: '0%', opacity: '1'}, 96 | {left: '30%', opacity: '0'}, 97 | ]; 98 | 99 | const sideBarTiming = { 100 | duration: 450, 101 | iterations: 1, 102 | easing: 'ease', 103 | }; 104 | 105 | const navBarSlideIn = [ 106 | {left: '-50%', opacity: '0'}, 107 | {left: '0%', opacity: '1'}, 108 | ]; 109 | 110 | const navBarSlideOut = [ 111 | {left: '0%', opacity: '1'}, 112 | {left: '-50%', opacity: '0'}, 113 | ]; 114 | 115 | const navBarAnimationOptions = { 116 | duration: 300, 117 | iterations: 1, 118 | easing: 'ease', 119 | }; 120 | 121 | const generators = getGeneratorsElement(); 122 | const sidebar = getResultPage(); 123 | const openSidePanelButton = getOpenSideBarButton(); 124 | 125 | const getHeaderText = document.getElementById('head'); 126 | const getResultsButton = document.querySelectorAll('[data-button]'); 127 | const getHomePage = document.getElementById('home-page'); 128 | const getGeneratorSection = document.getElementById('generator'); 129 | const getOpenPreviousResult = document.querySelector( 130 | '.open > p' 131 | ) as HTMLElement; 132 | const results = document.querySelectorAll('[data-result]'); 133 | const closeBar = document.getElementById('close-side-bar'); 134 | const getImageEntryElement = document.getElementById( 135 | `pic-text-file` 136 | ) as HTMLInputElement; 137 | const navBar = document.querySelector('#nav'); 138 | const menuIcon = document.querySelector('#menu-icon'); 139 | const downloadButtons = document.querySelectorAll( 140 | '.image-download' 141 | ) as NodeListOf; 142 | const dropDownElements = document.querySelectorAll('.dropdown'); 143 | const getDegreeElement = getRange('animation'); 144 | const getRadioButtonSetElement = getRadioButtonSet('animation'); 145 | const getDurationElement = getInputSpinner('animation'); 146 | const events = ['dragover', 'drop']; 147 | 148 | if (openSidePanelButton) { 149 | openSidePanelButton.style.display = 'none'; 150 | } 151 | 152 | if (!navigator.userAgent.match(/firefox|fxios/i)) { 153 | downloadButtons.forEach((item) => (item.style.display = 'none')); 154 | } 155 | 156 | FilePond.create(getImageEntryElement, { 157 | imagePreviewMaxHeight: 200, 158 | 159 | labelIdle: 160 | window.innerWidth < 768 161 | ? 'Browse' 162 | : 'Drag & Drop your files or Browse ', 163 | 164 | onpreparefile: (fileItem, output): void => { 165 | // create a new image object 166 | const img = new Image(); 167 | 168 | // Dirty trick to get the final visible height of the picture 169 | // Based on the knowledge the width will be 300px 170 | img.onload = () => { 171 | imageHeight = Math.floor( 172 | img.naturalHeight / (img.naturalWidth / FINAL_WIDTH) 173 | ); 174 | }; 175 | 176 | // set the image source to the output of the Image Transform plugin 177 | img.src = URL.createObjectURL(output); 178 | imageSRC = img.src; 179 | 180 | // reference to reset button 181 | const resetBtn = document.querySelector( 182 | "[data-reset='pic-text']" 183 | ) as HTMLButtonElement; 184 | 185 | // function to enable the get result button once image uploaded 186 | function enableImgResultBtn() { 187 | const getPicResultBtn = document.querySelector( 188 | '[data-button="pic-text"]' 189 | ) as HTMLButtonElement; 190 | 191 | getPicResultBtn.style.pointerEvents = ''; 192 | //add reset button to dom 193 | resetBtn.classList.add('reset-show'); 194 | } 195 | 196 | enableImgResultBtn(); 197 | 198 | // disable btn also when close btn clicked on image display 199 | const closeBtn = document.querySelector( 200 | '.filepond--action-remove-item' 201 | ) as HTMLButtonElement; 202 | 203 | closeBtn.addEventListener('click', function () { 204 | const getPicResultBtn = document.querySelector( 205 | '[data-button="pic-text"]' 206 | ) as HTMLButtonElement; 207 | 208 | getPicResultBtn.style.pointerEvents = 'none'; 209 | // remove reset button from dom 210 | resetBtn.classList.remove('reset-show'); 211 | }); 212 | 213 | // clear the input value when reset button is clicked. 214 | 215 | function resetValue() { 216 | resetBtn.addEventListener('click', () => { 217 | closeBtn.click(); 218 | }); 219 | } 220 | 221 | resetValue(); 222 | 223 | console.log(fileItem); 224 | }, 225 | }); 226 | 227 | const generatorParam = getQueryParam('generator'); 228 | const valuesParam = getQueryParam('values'); 229 | 230 | if (generatorParam) { 231 | showContent(generatorParam); 232 | 233 | if (valuesParam) { 234 | generatorParam === 'animation' && 235 | applyAnimationValues(JSON.parse(valuesParam)); 236 | generatorParam === 'border-radius' && 237 | applyBorderRadiusValues(JSON.parse(valuesParam)); 238 | generatorParam === 'box-shadow' && applyBoxShadowValues(valuesParam); 239 | generatorParam === 'gradient-background' && 240 | applyGradientValues(valuesParam, 'gradient-background'); 241 | generatorParam === 'gradient-border' && 242 | applyGradientValues(valuesParam, 'gradient-border'); 243 | generatorParam === 'gradient-text' && 244 | applyGradientValues(valuesParam, 'gradient-text'); 245 | generatorParam === 'grid-generators' && applyGridValues(valuesParam); 246 | generatorParam === 'input-range' && applyInputRangeValues(valuesParam); 247 | generatorParam === 'text-shadow' && applyTextShadowValues(valuesParam); 248 | generatorParam === 'transform' && applyTransformValue(valuesParam); 249 | } 250 | } 251 | 252 | /** 253 | * sets which generator to call 254 | * 255 | * @param attribute The attribute name of the generator element 256 | * @param type type of result to get back 257 | */ 258 | function generatorsFunction(attribute: string, type: openResults): void { 259 | // function to call based on generator 260 | attribute === 'pic-text' && picTextGenerator(imageSRC, imageHeight, type); 261 | attribute === 'gradient-text' && gradientTextGenerator(type); 262 | attribute === 'gradient-border' && gradientBorderGenerator(type); 263 | attribute === 'gradient-background' && gradientBackgroundGenerator(type); 264 | attribute === 'animation' && animationGenerator(type); 265 | attribute === 'border-radius' && borderRadiusGenerator(type); 266 | attribute === 'box-shadow' && boxShadowGenerator(type); 267 | attribute === 'text-shadow' && textShadowGenerator(type); 268 | attribute === 'transform' && transformGenerator(type); 269 | } 270 | 271 | /** 272 | * use to toggle visibility of content in generators 273 | * 274 | * @param attribute The attribute name of the generator element 275 | * @param display display type 276 | */ 277 | function showInputSection(attribute: string, display: Display): void { 278 | const generatorsContent = document.querySelectorAll(`[data-content]`); 279 | const showGen = document.querySelector( 280 | `[data-content=${attribute}]` 281 | ) as HTMLElement; 282 | const highLightGen = document.querySelector( 283 | `[data-gen=${attribute}]` 284 | ) as HTMLElement; 285 | 286 | generatorsContent.forEach((item) => { 287 | const element = item; 288 | element.style.display = 'none'; 289 | }); 290 | 291 | generators.forEach((item) => { 292 | const generatorNav = item; 293 | generatorNav.style.border = 'none'; 294 | generatorNav.style.background = 'none'; 295 | }); 296 | 297 | showGen.style.display = `${display}`; 298 | highLightGen.style.background = `linear-gradient(80deg,var(--primary-color), var(--secondary-color))`; 299 | highLightGen.style.border = '1px solid var(--tertiary-color)'; 300 | 301 | // function to call based on generator 302 | attribute === 'input-range' && rangeGenerator(); 303 | attribute === 'gradient-border' && addGradientBorderListener(); 304 | attribute === 'gradient-text' && addGradientTextListener(); 305 | attribute === 'border-radius' && addBorderRadiusListener(); 306 | attribute === 'text-shadow' && addTextShadowListener(); 307 | attribute === 'box-shadow' && addBoxShadowListener(); 308 | attribute === 'gradient-background' && addGradientBackgroundListener(); 309 | attribute === 'animation' && addAnimationListener(); 310 | attribute === 'transform' && addTransformListener(); 311 | attribute === 'grid-generators' && gridGenerator(); 312 | attribute === 'scroll' && scrollGenerator(); 313 | } 314 | 315 | /** 316 | * Which generator result should show 317 | * 318 | * @param attribute The attribute name of the generator element 319 | * @param type type of result to get back 320 | */ 321 | function showResult(attribute: string | null, type: openResults): void { 322 | results.forEach((item) => { 323 | const element = item; 324 | if (element.getAttribute('data-result') === attribute) { 325 | element.style.display = 'flex'; 326 | } else { 327 | element.style.display = 'none'; 328 | } 329 | }); 330 | if (attribute === null || type === null) return; 331 | 332 | generatorsFunction(attribute, type); 333 | } 334 | 335 | /** 336 | * Control Visibility of the Navigation Bar 337 | * 338 | * @param state state of visibility 339 | */ 340 | function openOrCloseNavigationBar(state: 'open' | 'close') { 341 | if (state === 'open') { 342 | navBar?.animate(navBarSlideIn, navBarAnimationOptions); 343 | navBar?.classList.remove('closed-nav'); 344 | menuIcon?.setAttribute('icon', 'ci:close-big'); 345 | } else if (state === 'close') { 346 | navBar?.animate(navBarSlideOut, navBarAnimationOptions); 347 | navBar?.classList.add('closed-nav'); 348 | menuIcon?.setAttribute('icon', 'dashicons:menu-alt'); 349 | } 350 | } 351 | 352 | function showOpenPreviousResultText() { 353 | getOpenPreviousResult.style.display = 'block'; 354 | getOpenPreviousResult.style.animationName = 'showOpenPreviousResultText'; 355 | getOpenPreviousResult.style.animationDuration = '1500ms'; 356 | getOpenPreviousResult.style.animationTimingFunction = 'ease-in'; 357 | getOpenPreviousResult.style.animationFillMode = 'backwards'; 358 | } 359 | 360 | function showContent(generatorName: string) { 361 | !navBar?.classList.contains('closed-nav') && 362 | openOrCloseNavigationBar('close'); 363 | 364 | sidebar.style.display = 'none'; 365 | 366 | if (getHomePage && getGeneratorSection) { 367 | getHomePage.style.display = 'none'; 368 | getGeneratorSection.style.display = 'flex'; 369 | showInputSection(generatorName, 'flex'); 370 | } 371 | } 372 | 373 | // clicking outside the nav bar should close the nav bar 374 | document.addEventListener('click', (e: Event) => { 375 | const event = e.target as HTMLElement; 376 | 377 | const areasNotAllowedToCloseNavigationBar = { 378 | isNotNavigationBar: !event.matches('nav'), 379 | isNotDropDownLabel: !event.matches('.dropdown'), 380 | isNotMenuIcon: !event.matches('#menu-icon'), 381 | isParentNotDropDownLabel: 382 | !event.parentElement?.parentElement?.matches('.dropdown'), 383 | doesNotHaveAClosedClass: !navBar?.classList.contains('closed-nav'), 384 | isNotFooter: !event.matches('footer'), 385 | isNotCategoryName: !event.matches('.dropdown > div'), 386 | }; 387 | 388 | const isAllValidationTrue = Object.values( 389 | areasNotAllowedToCloseNavigationBar 390 | ).every((value) => value === true); 391 | 392 | if (isAllValidationTrue) { 393 | openOrCloseNavigationBar('close'); 394 | } 395 | }); 396 | 397 | // Disable file opening in browser 398 | for (let event of events) { 399 | document.addEventListener(event, (e) => { 400 | e.preventDefault(); 401 | }); 402 | } 403 | 404 | // clicking on the menu icon should close the nav bar 405 | menuIcon?.addEventListener('click', () => { 406 | if (navBar?.classList.contains('closed-nav')) { 407 | openOrCloseNavigationBar('open'); 408 | } else { 409 | openOrCloseNavigationBar('close'); 410 | } 411 | }); 412 | 413 | //event listener to clear styling on generator tabs when "Code magic is clicked" 414 | getHeaderText?.addEventListener('click', () => { 415 | if (getHomePage === null || getGeneratorSection === null) return; 416 | generators.forEach((item) => { 417 | const generatorNav = item; 418 | generatorNav.style.border = 'none'; 419 | generatorNav.style.background = 'none'; 420 | if (generatorNav.parentElement === null) return; 421 | generatorNav.parentElement.id = ''; 422 | }); 423 | getHomePage.style.display = 'flex'; 424 | getGeneratorSection.style.display = 'none'; 425 | 426 | deleteQueryParam('generator'); 427 | deleteQueryParam('values'); 428 | }); 429 | 430 | // clicking on the get result icon should show the old results 431 | openSidePanelButton?.addEventListener('click', () => { 432 | showResult(attributeValue, 'oldResults'); 433 | sidebar.animate(sideBarSlide, sideBarTiming); 434 | sidebar.style.left = '0%'; 435 | }); 436 | 437 | // onClick event listener for the closebar icon 438 | closeBar?.addEventListener('click', () => { 439 | sidebar.animate(sideBarSlideOut, sideBarTiming); 440 | sidebar.style.left = '100%'; 441 | showResult(null, null); 442 | setTimeout(() => { 443 | sidebar.style.display = 'none'; 444 | }, 600); 445 | 446 | setTimeout(() => { 447 | showOpenPreviousResultText(); 448 | }, 200); 449 | }); 450 | 451 | getDurationElement?.addEventListener('change', () => { 452 | displayAnimationPreview(); 453 | }); 454 | 455 | getDegreeElement?.addEventListener('change', () => displayAnimationPreview()); 456 | 457 | // adds event listner for which generator should show 458 | generators.forEach((generator) => { 459 | generator?.addEventListener('click', (): void => { 460 | const generatorName = generator.getAttribute('data-gen'); 461 | openSidePanelButton.style.display = 'none'; 462 | 463 | if (generatorName === null) return; 464 | 465 | showContent(generatorName); 466 | 467 | deleteQueryParam('values'); 468 | setQueryParam('generator', generatorName); 469 | }); 470 | }); 471 | 472 | // event listener for get result button 473 | getResultsButton.forEach((getResult) => { 474 | getResult?.addEventListener('click', () => { 475 | openSidePanelButton.style.display = 'flex'; 476 | 477 | showResult(getResult.getAttribute('data-button'), 'newResults'); 478 | sidebar.animate(sideBarSlide, sideBarTiming); 479 | sidebar.style.left = '0%'; 480 | }); 481 | }); 482 | 483 | getRadioButtonSetElement.forEach((radioButton: HTMLInputElement) => { 484 | radioButton.onclick = () => { 485 | displayAnimationPreview(); 486 | }; 487 | }); 488 | 489 | // configuring dropdown menu 490 | dropDownElements.forEach((dropDown) => { 491 | // add click event listener to the dropdown parent element 492 | dropDown.addEventListener('click', (e) => { 493 | e.stopPropagation(); 494 | 495 | const listElement = dropDown.lastElementChild as HTMLElement; 496 | const iconElement = 497 | listElement?.parentElement?.firstElementChild?.lastElementChild; 498 | if (listElement.id === 'showList') { 499 | listElement.id = ''; 500 | 501 | // Toggle arrow icon to downwards 502 | if (iconElement) { 503 | iconElement.setAttribute( 504 | 'icon', 505 | 'material-symbols:arrow-drop-down-rounded' 506 | ); 507 | } 508 | return; 509 | } 510 | 511 | // clear other open dropdown menus 512 | dropDownElements.forEach((dropdown) => { 513 | const listElement = dropdown.lastElementChild as HTMLElement; 514 | listElement.id = ''; 515 | }); 516 | 517 | listElement.id = 'showList'; 518 | 519 | // Toggle arrow icon to upwards 520 | if (iconElement) { 521 | iconElement.setAttribute( 522 | 'icon', 523 | 'material-symbols:arrow-drop-up-rounded' 524 | ); 525 | } 526 | }); 527 | 528 | const listElement = dropDown.lastElementChild as HTMLElement; 529 | 530 | // Prevent the click event on subitems from propagating to the parent dropdown 531 | listElement.addEventListener('click', (e) => { 532 | e.stopPropagation(); 533 | }); 534 | 535 | // loop through children of dropdown and add event listener to each child 536 | for (let i = 0; i < listElement.children.length; i++) { 537 | const child = listElement.children[i] as HTMLElement; 538 | child.addEventListener('click', () => { 539 | listElement.id = 'showList'; 540 | // loop through all dropdown elements and check border of children 541 | dropDownElements.forEach((dropDown) => { 542 | const currentListElement = dropDown.lastElementChild as HTMLElement; 543 | let hasBorder = false; 544 | for (let i = 0; i < currentListElement.children.length; i++) { 545 | const currentChild = currentListElement.children[i] as HTMLElement; 546 | if (currentChild.style.border === '1px solid var(--tertiary-color)') { 547 | hasBorder = true; 548 | break; 549 | } 550 | } 551 | if (!hasBorder) { 552 | currentListElement.id = ''; 553 | } 554 | }); 555 | }); 556 | } 557 | }); 558 | 559 | showResult(null, null); 560 | -------------------------------------------------------------------------------- /src/pages/animation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | closeDropdown, 3 | setGradientDegreeValue, 4 | showPopup, 5 | slideIn, 6 | } from '../lib/packages/utils'; 7 | import {deleteQueryParam, setQueryParam} from '../lib/packages/helpers'; 8 | import { 9 | getAllFields, 10 | getCopyCodeButton, 11 | getCssOrTailwindButton, 12 | getCssOrTailwindDropdown, 13 | getDegreeSpanElement, 14 | getInputSpinner, 15 | getOutput, 16 | getPreviewSlider, 17 | getRadioButtonSet, 18 | getRange, 19 | getResetButton, 20 | getResultPage, 21 | getStyleSheet, 22 | getTailwindButton, 23 | } from '../lib/getElements'; 24 | 25 | import copy from 'copy-to-clipboard'; 26 | 27 | let initial_length = 0; 28 | // let rule_added = false; 29 | let isAnimationSliderOpen = false; 30 | let css = ''; 31 | let tailwindCss = ''; 32 | 33 | type Values = { 34 | type: string; 35 | degree: string; 36 | duration: string; 37 | }; 38 | 39 | const attribute = 'animation'; 40 | const getCodeButtonElement = getCopyCodeButton(attribute); 41 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 42 | 43 | const getOutputElement = getOutput(attribute); 44 | const getDegreeElement = getRange(attribute); 45 | const getRadioButtonSetElement = getRadioButtonSet(attribute); 46 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 47 | const showCopyClass = 'show-css-tailwind'; 48 | 49 | const preview = getPreviewSlider(attribute); 50 | 51 | function getCssOrTailwind(e?: MouseEvent): void { 52 | e?.stopPropagation(); 53 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 54 | } 55 | 56 | // closes css and tailwind dropdown on outside click 57 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 58 | 59 | initialConfiguration( 60 | getRadioButtonSetElement, 61 | getDegreeElement, 62 | getOutputElement 63 | ); 64 | 65 | export function animationGenerator(type: 'newResults' | 'oldResults' | null) { 66 | if (type === null) return; 67 | 68 | const duration = getInputSpinner(attribute); 69 | 70 | const Stylesheet = getStyleSheet(); 71 | const resultPage = getResultPage(); 72 | 73 | resultPage.style.display = 'flex'; 74 | 75 | initial_length = Stylesheet.cssRules.length - 1; 76 | 77 | if (getOutputElement === null || type === 'oldResults') return; 78 | 79 | let selectedIndex = 0; 80 | 81 | for (let i = 0; i < getRadioButtonSetElement.length; i++) { 82 | if (getRadioButtonSetElement[i].checked) { 83 | selectedIndex = i; 84 | break; 85 | } 86 | } 87 | 88 | const values: Values = { 89 | type: getRadioButtonSetElement[selectedIndex].value, 90 | degree: getDegreeElement.value, 91 | duration: duration.value, 92 | }; 93 | 94 | getCodeButtonElement.addEventListener('click', () => { 95 | copy(css); 96 | showPopup( 97 | 'Code Copied', 98 | 'Code has been successfully copied to clipboard', 99 | 'success' 100 | ); 101 | }); 102 | manageAnimation(values, getOutputElement, Stylesheet); 103 | getTailwindCodeButtonElement.addEventListener('click', () => { 104 | copy(tailwindCss); 105 | showPopup( 106 | 'Tailwind Code Copied', 107 | 'Code has been successfully copied to clipboard', 108 | 'success' 109 | ); 110 | }); 111 | manageTailwindAnimation(values); 112 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 113 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 114 | } 115 | 116 | // configuring animation preview 117 | export function displayAnimationPreview() { 118 | slideIn(preview, isAnimationSliderOpen); 119 | isAnimationSliderOpen = true; 120 | 121 | if (preview.getAttribute('data-running') !== 'true') { 122 | const duration = getInputSpinner(attribute); 123 | 124 | const Stylesheet = getStyleSheet(); 125 | 126 | let i = 0; 127 | 128 | for (i = 0; i < getRadioButtonSetElement.length; i++) 129 | if (getRadioButtonSetElement[i].checked) break; 130 | 131 | const values: Values = { 132 | type: getRadioButtonSetElement[i].value, 133 | degree: getDegreeElement.value, 134 | duration: duration.value, 135 | }; 136 | 137 | setQueryParam('values', JSON.stringify(values)); 138 | 139 | // only updating preview animation if no current animation is running 140 | preview.onanimationend = () => { 141 | preview.setAttribute('data-running', 'false'); 142 | preview.style.animation = ''; 143 | Stylesheet.deleteRule(initial_length + 1); 144 | }; 145 | 146 | preview.setAttribute('data-running', 'true'); 147 | manageAnimation(values, preview, Stylesheet); 148 | } 149 | } 150 | 151 | /** 152 | * sets the animation to the output element 153 | * 154 | * @param values object that contains all values entered by users 155 | * @param getOutputElement output element to display result 156 | * @param stylesheet css stylesheet 157 | */ 158 | function manageAnimation( 159 | values: Values, 160 | getOutputElement: HTMLElement, 161 | stylesheet: CSSStyleSheet 162 | ) { 163 | // if (rule_added) { 164 | // stylesheet.deleteRule(initial_length + 1); 165 | // rule_added = false; 166 | // } 167 | if (values.type === 'fade') { 168 | css = 169 | `/*Copy and paste keyframe into your css file, and apply the animation property in the element of your choice*/\n` + 170 | `@keyframes flickerAnimation { \n` + 171 | `0% { opacity:${values.degree}; }\n` + 172 | `50% { opacity:0; }\n` + 173 | `100% { opacity:${values.degree}; }\n` + 174 | `}\n` + 175 | `animation: flickerAnimation ${values.duration}s infinite;`; 176 | stylesheet.insertRule( 177 | `@keyframes flickerAnimation { \n` + 178 | `0% { opacity:${values.degree}; }\n` + 179 | `50% { opacity:0; }\n` + 180 | `100% { opacity:${values.degree}; }\n` + 181 | `}`, 182 | initial_length + 1 183 | ); 184 | 185 | getOutputElement.style.animation = `flickerAnimation ease-in`; 186 | getOutputElement.style.animationDuration = `${values.duration}s`; 187 | } else if (values.type === 'skew') { 188 | css = 189 | `/*Copy and paste keyframe into your css file, and apply the animation property in the element of your choice*/\n` + 190 | `@keyframes skewAnimation { \n` + 191 | `0% { transform:skew(${values.degree}deg); }\n` + 192 | `50% { transform:skew(0deg); }\n` + 193 | `100% { transform:skew(${values.degree}deg); }\n` + 194 | `}\n` + 195 | `animation:skewAnimation ${values.duration}s infinite;`; 196 | 197 | stylesheet.insertRule( 198 | `@keyframes skewAnimation { \n` + 199 | `0% { transform:skew(${values.degree}deg); }\n` + 200 | `50% { transform:skew(0deg); }\n` + 201 | `100% { transform:skew(${values.degree}deg); }\n` + 202 | `}`, 203 | initial_length + 1 204 | ); 205 | 206 | getOutputElement.style.animation = `skewAnimation ease-in`; 207 | getOutputElement.style.animationDuration = `${values.duration}s`; 208 | // rule_added = true; 209 | } else if (values.type === 'flip') { 210 | css = 211 | `/*Copy and paste keyframe into your css file, and apply the animation property in the element of your choice*/\n` + 212 | `@keyframes turnaround { \n` + 213 | `0% { transform:rotateY(${values.degree}deg); }\n` + 214 | `50% { transform:rotateY(0deg); }\n` + 215 | `100% { transform:rotateY(${values.degree}deg); }\n` + 216 | `}\n` + 217 | `animate: turnaround ${values.duration}s infinite`; 218 | 219 | stylesheet.insertRule( 220 | `@keyframes turnaround { \n` + 221 | `0% { transform:rotateY(${values.degree}deg); }\n` + 222 | `50% { transform:rotateY(0deg); }\n` + 223 | `100% { transform:rotateY(${values.degree}deg); }\n` + 224 | `}`, 225 | initial_length + 1 226 | ); 227 | 228 | getOutputElement.style.animation = `turnaround ease-in`; 229 | getOutputElement.style.animationDuration = `${values.duration}s`; 230 | 231 | // rule_added = true; 232 | } else if (values.type === 'rotate') { 233 | css = 234 | `/*Copy and paste keyframe into your css file, and apply the animation property in the element of your choice*/\n` + 235 | `@keyframes rotate { \n` + 236 | `0% { transform:rotate(0deg); }\n` + 237 | `100% { transform:rotate(${values.degree}deg); }\n` + 238 | `}\n` + 239 | `animate: rotate ${values.duration}s infinite`; 240 | 241 | stylesheet.insertRule( 242 | `@keyframes rotate { \n` + 243 | `0% { transform:rotate(0deg); }\n` + 244 | `100% { transform:rotate(${values.degree}deg); }\n` + 245 | `}`, 246 | initial_length + 1 247 | ); 248 | 249 | getOutputElement.style.animation = `rotate ease-in`; 250 | getOutputElement.style.animationDuration = `${values.duration}s`; 251 | } 252 | } 253 | 254 | /** 255 | * sets the inital configuration of the elements 256 | * 257 | * @param elements radio elements 258 | * @param getDegreeElement degree element 259 | * @param getOutputElement output element to display result 260 | */ 261 | function initialConfiguration( 262 | elements: NodeListOf, 263 | getDegreeElement: HTMLInputElement, 264 | getOutputElement: HTMLElement 265 | ): void { 266 | if (getOutputElement === null) return; 267 | getOutputElement.style.display = 'flex'; 268 | getOutputElement.style.justifyContent = 'center'; 269 | getOutputElement.style.alignItems = 'center'; 270 | getOutputElement.style.fontSize = '1.1em'; 271 | getOutputElement.style.fontWeight = '700'; 272 | 273 | getOutputElement.innerText = 'Lorem Ipsum'; 274 | 275 | // get the unit display element for animator 276 | const unitDisplayElement = document.querySelector( 277 | '.unit-display.animation' 278 | ) as HTMLElement; 279 | 280 | const titleDisplayElement = document.querySelector( 281 | '.title-display' 282 | ) as HTMLElement; 283 | 284 | elements.forEach((el) => 285 | el.addEventListener('click', () => { 286 | const type = el.value; 287 | if (type === 'skew' || type === 'flip') { 288 | getDegreeElement.min = '-90'; 289 | getDegreeElement.max = '90'; 290 | getDegreeElement.step = '1'; 291 | getDegreeElement.value = '50'; 292 | unitDisplayElement.innerText = `${getDegreeElement.value}deg`; 293 | titleDisplayElement.innerText = 'Angle'; 294 | } else if (type === 'rotate') { 295 | getDegreeElement.min = '0'; 296 | getDegreeElement.max = '360'; 297 | getDegreeElement.step = '1'; 298 | getDegreeElement.value = '45'; 299 | unitDisplayElement.innerText = `${getDegreeElement.value}deg`; 300 | titleDisplayElement.innerText = 'Degrees'; 301 | } else { 302 | getDegreeElement.min = '0'; 303 | getDegreeElement.max = '1'; 304 | getDegreeElement.step = '.1'; 305 | getDegreeElement.value = '.5'; 306 | unitDisplayElement.innerText = `${getDegreeElement.value}`; 307 | titleDisplayElement.innerText = 'Opacity'; 308 | } 309 | }) 310 | ); 311 | } 312 | 313 | export function addAnimationListener() { 314 | setGradientDegreeValue(getDegreeElement); 315 | } 316 | 317 | function resetValues() { 318 | const {inputs} = getAllFields(attribute); 319 | 320 | getResetButton(attribute).addEventListener('click', () => { 321 | inputs.forEach((input) => { 322 | input.value = input.defaultValue; 323 | input.checked = input.defaultChecked; 324 | }); 325 | 326 | getDegreeSpanElement(attribute).innerHTML = 'deg'; 327 | getResetButton(attribute).classList.remove('reset-show'); 328 | }); 329 | 330 | deleteQueryParam('values'); 331 | } 332 | 333 | // get values from all targets to get notified when values change. 334 | 335 | function getValues() { 336 | const {inputs} = getAllFields(attribute); 337 | 338 | inputs.forEach((input) => { 339 | input.addEventListener('input', () => { 340 | if ( 341 | inputs[4].value !== inputs[4].defaultValue || 342 | input.checked !== input.defaultChecked || 343 | inputs[5].value !== inputs[5].defaultValue 344 | ) { 345 | getResetButton(attribute).classList.add('reset-show'); 346 | resetValues(); 347 | } 348 | }); 349 | }); 350 | } 351 | getValues(); 352 | 353 | // Function to get tailwind styles for animation 354 | function manageTailwindAnimation(values: Values) { 355 | // if (rule_added) { 356 | // stylesheet.deleteRule(initial_length + 1); 357 | // rule_added = false; 358 | // } 359 | if (values.type === 'fade') { 360 | tailwindCss = ``; 361 | } else if (values.type === 'skew') { 362 | tailwindCss = ``; 363 | // rule_added = true; 364 | } else if (values.type === 'flip') { 365 | tailwindCss = ``; 366 | // rule_added = true; 367 | } else if (values.type === 'rotate') { 368 | tailwindCss = ``; 369 | } 370 | } 371 | 372 | export const applyAnimationValues = (values: Values) => { 373 | const duration = getInputSpinner(attribute); 374 | const unitDisplayElement = document.querySelector( 375 | '.unit-display.animation' 376 | ) as HTMLElement; 377 | 378 | for (let i = 0; i < getRadioButtonSetElement.length; i++) { 379 | const selectedRadioElemeent = getRadioButtonSetElement[i]; 380 | if (selectedRadioElemeent.value === values.type) { 381 | selectedRadioElemeent.checked = true; 382 | break; 383 | } 384 | } 385 | 386 | getDegreeElement.value = values.degree; 387 | unitDisplayElement.innerText = `${values.degree}deg`; 388 | duration.value = values.duration; 389 | 390 | displayAnimationPreview(); 391 | }; 392 | -------------------------------------------------------------------------------- /src/pages/border-radius.ts: -------------------------------------------------------------------------------- 1 | import { 2 | closeDropdown, 3 | copyCSSCodeToClipboard, 4 | copyTailwindCodeToClipboard, 5 | showPopup, 6 | } from '../lib/packages/utils'; 7 | import {deleteQueryParam, setQueryParam} from '../lib/packages/helpers'; 8 | import { 9 | getAllFields, 10 | getBorderBottom, 11 | getBorderLeft, 12 | getBorderRight, 13 | getBorderTop, 14 | getCopyCodeButton, 15 | getCssOrTailwindButton, 16 | getCssOrTailwindDropdown, 17 | getOutput, 18 | getResetButton, 19 | getResultPage, 20 | getTailwindButton, 21 | } from '../lib/getElements'; 22 | 23 | type Values = { 24 | borderTop: string; 25 | borderLeft: string; 26 | borderRight: string; 27 | borderBottom: string; 28 | }; 29 | 30 | const attribute = 'border-radius'; 31 | 32 | const borderRadiusInputs = document.querySelectorAll('.border-radius-inputs'); 33 | const borderTop = getBorderTop(attribute); 34 | const borderRight = getBorderRight(attribute); 35 | const borderLeft = getBorderLeft(attribute); 36 | const borderBottom = getBorderBottom(attribute); 37 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 38 | const showCopyClass = 'show-css-tailwind'; 39 | 40 | const borderRadiusPreview = document.querySelector( 41 | '.border-radius-preview-box > .preview' 42 | ) as HTMLElement; 43 | 44 | function copyHandler() { 45 | const outputElement = getOutput(attribute); 46 | copyCSSCodeToClipboard(attribute, outputElement); 47 | showPopup( 48 | 'Code Copied', 49 | 'Code has been successfully copied to clipboard', 50 | 'success' 51 | ); 52 | } 53 | 54 | function getCssOrTailwind(e?: MouseEvent): void { 55 | e?.stopPropagation(); 56 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 57 | } 58 | 59 | // closes css and tailwind dropdown on outside click 60 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 61 | 62 | function getBorderRadiusResult( 63 | attribute: string, 64 | values: Values, 65 | outputElement: HTMLElement 66 | ): void { 67 | outputElement.style.width = '250px'; 68 | outputElement.style.height = '250px'; 69 | outputElement.style.borderRadius = `${values.borderTop}% ${ 70 | 100 - Number(values.borderTop) 71 | }% 72 | ${values.borderBottom}% ${100 - Number(values.borderBottom)}% / 73 | ${values.borderLeft}% ${values.borderRight}% 74 | ${100 - Number(values.borderRight)}% ${100 - Number(values.borderLeft)}%`; 75 | 76 | const getCodeButtonElement = getCopyCodeButton(attribute); 77 | getCodeButtonElement.addEventListener('click', copyHandler); 78 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 79 | getTailwindCodeButtonElement.addEventListener('click', tailwindHandler); 80 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 81 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 82 | } 83 | 84 | export function borderRadiusGenerator( 85 | type: 'newResults' | 'oldResults' | null 86 | ): void { 87 | if (type === null) return; 88 | 89 | const getOutputElement = getOutput(attribute); 90 | const resultPage = getResultPage(); 91 | 92 | resultPage.style.display = 'flex'; 93 | if (getOutputElement === null || type === 'oldResults') return; 94 | getOutputElement.style.display = 'grid'; 95 | getOutputElement.style.placeItems = 'center'; 96 | 97 | const values = { 98 | borderTop: borderTop.value, 99 | borderLeft: borderLeft.value, 100 | borderRight: borderRight.value, 101 | borderBottom: borderBottom.value, 102 | }; 103 | 104 | getBorderRadiusResult(attribute, values, getOutputElement); 105 | } 106 | 107 | export function addBorderRadiusListener() { 108 | const setBorderRadiusPreview = ( 109 | borderTop: HTMLInputElement, 110 | borderBottom: HTMLInputElement, 111 | borderLeft: HTMLInputElement, 112 | borderRight: HTMLInputElement 113 | ) => { 114 | borderRadiusPreview.style.borderRadius = ` 115 | ${borderTop.value}% ${100 - Number(borderTop.value)}% 116 | ${borderBottom.value}% ${100 - Number(borderBottom.value)}% / 117 | ${borderLeft.value}% ${borderRight.value}% 118 | ${100 - Number(borderRight.value)}% ${100 - Number(borderLeft.value)}%`; 119 | }; 120 | 121 | borderRadiusInputs.forEach((inputElement) => { 122 | inputElement.addEventListener('input', () => { 123 | const values = { 124 | borderTop: borderTop.value, 125 | borderLeft: borderLeft.value, 126 | borderRight: borderRight.value, 127 | borderBottom: borderBottom.value, 128 | }; 129 | setBorderRadiusPreview(borderTop, borderBottom, borderLeft, borderRight); 130 | 131 | setQueryParam('values', JSON.stringify(values)); 132 | }); 133 | }); 134 | } 135 | 136 | // reset the values of all target fields 137 | function resetValues() { 138 | const {inputs} = getAllFields(attribute); 139 | 140 | getResetButton(attribute).addEventListener('click', () => { 141 | inputs.forEach((input) => { 142 | input.value = input.defaultValue; 143 | }); 144 | 145 | borderRadiusPreview.style.borderRadius = '0'; 146 | 147 | getResetButton(attribute).classList.remove('reset-show'); 148 | }); 149 | 150 | deleteQueryParam('values'); 151 | } 152 | 153 | // get values from all targets to get notified when values change. 154 | function getValues() { 155 | const {inputs} = getAllFields(attribute); 156 | 157 | inputs.forEach((input) => { 158 | input.addEventListener('input', () => { 159 | getResetButton(attribute).classList.add('reset-show'); 160 | resetValues(); 161 | }); 162 | }); 163 | } 164 | getValues(); 165 | 166 | // Tailwind codecopy handler 167 | function tailwindHandler() { 168 | const outputElement = getOutput(attribute); 169 | copyTailwindCodeToClipboard(attribute, outputElement); 170 | showPopup( 171 | 'Tailwind Code Copied', 172 | 'Code has been successfully copied to clipboard', 173 | 'success' 174 | ); 175 | } 176 | 177 | export const applyBorderRadiusValues = (values: Values) => { 178 | borderTop.value = values.borderTop; 179 | borderLeft.value = values.borderLeft; 180 | borderRight.value = values.borderRight; 181 | borderBottom.value = values.borderBottom; 182 | 183 | borderRadiusPreview.style.borderRadius = ` 184 | ${borderTop.value}% ${100 - Number(borderTop.value)}% 185 | ${borderBottom.value}% ${100 - Number(borderBottom.value)}% / 186 | ${borderLeft.value}% ${borderRight.value}% 187 | ${100 - Number(borderRight.value)}% ${100 - Number(borderLeft.value)}%`; 188 | }; 189 | -------------------------------------------------------------------------------- /src/pages/box-shadow.ts: -------------------------------------------------------------------------------- 1 | import { 2 | closeDropdown, 3 | copyCSSCodeToClipboard, 4 | copyTailwindCodeToClipboard, 5 | showPopup, 6 | slideIn, 7 | } from '../lib/packages/utils'; 8 | import {deleteQueryParam, setQueryParam} from '../lib/packages/helpers'; 9 | import { 10 | getAllFields, 11 | getAllInputElements, 12 | getCopyCodeButton, 13 | getCssOrTailwindButton, 14 | getCssOrTailwindDropdown, 15 | getOpenSideBarButton, 16 | getOutput, 17 | getPreviewSlider, 18 | getResetButton, 19 | getResultPage, 20 | getShadowBlur, 21 | getShadowColor, 22 | getShadowFields, 23 | getShadowHorizontalOffset, 24 | getShadowSpread, 25 | getShadowVerticalOffset, 26 | getTailwindButton, 27 | } from '../lib/getElements'; 28 | 29 | type Values = { 30 | hOffset: string; 31 | vOffset: string; 32 | blur: string; 33 | spread: string; 34 | color: string; 35 | }; 36 | 37 | const attribute = 'box-shadow'; 38 | let isSliderOpen = false; 39 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 40 | const showCopyClass = 'show-css-tailwind'; 41 | const boxshadowInputs = getAllInputElements(attribute); 42 | const preview = getPreviewSlider(attribute); 43 | const horizontalOffset = getShadowHorizontalOffset(attribute); 44 | const verticalOffset = getShadowVerticalOffset(attribute); 45 | const blur = getShadowBlur(attribute); 46 | const spread = getShadowSpread(attribute); 47 | const color = getShadowColor(attribute); 48 | 49 | function copyHandler() { 50 | const outputElement = getOutput(attribute); 51 | copyCSSCodeToClipboard(attribute, outputElement); 52 | showPopup( 53 | 'Code Copied', 54 | 'Code has been successfully copied to clipboard', 55 | 'success' 56 | ); 57 | } 58 | 59 | function getCssOrTailwind(e?: MouseEvent): void { 60 | e?.stopPropagation(); 61 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 62 | } 63 | 64 | // closes css and tailwind dropdown on outside click 65 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 66 | 67 | export function boxShadowGenerator( 68 | type: 'newResults' | 'oldResults' | null 69 | ): void { 70 | if (type === null) return; 71 | 72 | const getOutputElement = getOutput(attribute); 73 | const resultPage = getResultPage(); 74 | 75 | const element = boxshadowInputs[0]; 76 | const value = element.value; 77 | 78 | if (value.length < 3) { 79 | getOpenSideBarButton().style.display = 'none'; 80 | } else { 81 | getOpenSideBarButton().style.display = 'flex'; 82 | resultPage.style.display = 'flex'; 83 | } 84 | 85 | if (type === 'oldResults') return; 86 | 87 | const values: Values = { 88 | hOffset: horizontalOffset.value, 89 | vOffset: verticalOffset.value, 90 | blur: blur.value, 91 | spread: spread.value, 92 | color: color.value, 93 | }; 94 | 95 | getBoxShadowResult(values, getOutputElement); 96 | } 97 | 98 | /** 99 | * sets the result to the output element 100 | * 101 | * @param attribute attribute name of the generator 102 | * @param values values entered by users 103 | * @param outputElement output element to display result 104 | */ 105 | function getBoxShadowResult(values: Values, outputElement: HTMLElement): void { 106 | const createBoxShadowElement = ( 107 | boxShadowElement: HTMLElement, 108 | values: Values 109 | ) => { 110 | boxShadowElement.style.height = '300px'; 111 | boxShadowElement.style.width = '300px'; 112 | boxShadowElement.style.background = 'transparent'; 113 | boxShadowElement.style.boxShadow = `${values.hOffset}px ${values.vOffset}px ${values.blur}px ${values.spread}px ${values.color}`; 114 | }; 115 | createBoxShadowElement(outputElement, values); 116 | 117 | const getCodeButtonElement = getCopyCodeButton(attribute); 118 | getCodeButtonElement.addEventListener('click', copyHandler); 119 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 120 | getTailwindCodeButtonElement.addEventListener('click', tailwindHandler); 121 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 122 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 123 | } 124 | 125 | export function addBoxShadowListener(): void { 126 | const allBoxShadowInputs = [ 127 | horizontalOffset, 128 | verticalOffset, 129 | blur, 130 | spread, 131 | color, 132 | ]; 133 | const allBoxShadowInputsFields = getShadowFields(attribute, [ 134 | 'h-offset', 135 | 'v-offset', 136 | 'blur', 137 | 'spread', 138 | ]); 139 | 140 | const getShadowValue = () => 141 | `${horizontalOffset.value}px ${verticalOffset.value}px ${blur.value}px ${spread.value}px ${color.value}`; 142 | preview.style.boxShadow = getShadowValue(); 143 | 144 | allBoxShadowInputs.forEach((input, index) => { 145 | // default 146 | if (index < 4) { 147 | allBoxShadowInputsFields[index].textContent = `${input.value}px`; 148 | } 149 | input.addEventListener('input', () => { 150 | slideIn(preview, isSliderOpen); 151 | 152 | isSliderOpen = true; 153 | if (index < 4) { 154 | allBoxShadowInputsFields[index].textContent = `${input.value}px`; 155 | } 156 | setQueryParam('values', getShadowValue().trim().replaceAll(' ', '')); 157 | preview.style.boxShadow = getShadowValue(); 158 | }); 159 | }); 160 | } 161 | 162 | // reset the values of all target fields 163 | 164 | function resetValues() { 165 | const {inputs} = getAllFields(attribute); 166 | 167 | getResetButton(attribute).addEventListener('click', () => { 168 | inputs.forEach((input) => { 169 | input.value = input.defaultValue; 170 | }); 171 | 172 | document.querySelector( 173 | "[data-content='box-shadow'] #box-shadow-h-offset-field" 174 | )!.innerHTML = '5px'; 175 | document.querySelector( 176 | "[data-content='box-shadow'] #box-shadow-v-offset-field" 177 | )!.innerHTML = '10px'; 178 | document.querySelector( 179 | "[data-content='box-shadow'] #box-shadow-blur-field" 180 | )!.innerHTML = '18px'; 181 | document.querySelector( 182 | "[data-content='box-shadow'] #box-shadow-spread-field" 183 | )!.innerHTML = '5px'; 184 | 185 | deleteQueryParam('values'); 186 | getResetButton(attribute).classList.remove('reset-show'); 187 | }); 188 | } 189 | 190 | // get values from all targets to get notified when values change. 191 | 192 | function getValues() { 193 | const {inputs} = getAllFields(attribute); 194 | 195 | inputs.forEach((input) => { 196 | input.addEventListener('input', () => { 197 | getResetButton(attribute).classList.add('reset-show'); 198 | resetValues(); 199 | }); 200 | }); 201 | } 202 | getValues(); 203 | 204 | // Tailwind codecopy handler 205 | function tailwindHandler() { 206 | const outputElement: HTMLElement = getOutput(attribute); 207 | copyTailwindCodeToClipboard(attribute, outputElement); 208 | showPopup( 209 | 'Tailwind Code Copied', 210 | 'Code has been successfully copied to clipboard', 211 | 'success' 212 | ); 213 | } 214 | 215 | export const applyBoxShadowValues = (values: string) => { 216 | values = values.replaceAll('px', ' '); 217 | const [ 218 | horizontalOffsetValue, 219 | verticalOffsetValue, 220 | blurValue, 221 | spreadValue, 222 | colorValue, 223 | ] = values.split(' '); 224 | 225 | horizontalOffset.value = horizontalOffsetValue; 226 | verticalOffset.value = verticalOffsetValue; 227 | blur.value = blurValue; 228 | spread.value = spreadValue; 229 | color.value = colorValue; 230 | 231 | document.querySelector( 232 | "[data-content='box-shadow'] #box-shadow-h-offset-field" 233 | )!.innerHTML = horizontalOffsetValue + 'px'; 234 | document.querySelector( 235 | "[data-content='box-shadow'] #box-shadow-v-offset-field" 236 | )!.innerHTML = verticalOffsetValue + 'px'; 237 | document.querySelector( 238 | "[data-content='box-shadow'] #box-shadow-blur-field" 239 | )!.innerHTML = blurValue + 'px'; 240 | document.querySelector( 241 | "[data-content='box-shadow'] #box-shadow-spread-field" 242 | )!.innerHTML = spreadValue + 'px'; 243 | 244 | slideIn(preview, isSliderOpen); 245 | 246 | isSliderOpen = true; 247 | 248 | preview.style.boxShadow = values.replaceAll(' ', 'px '); 249 | }; 250 | -------------------------------------------------------------------------------- /src/pages/gradient-background.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addEventListenerToTheNewColorPicker, 3 | addNewColorPicker, 4 | closeDropdown, 5 | copyCSSCodeToClipboard, 6 | copyTailwindCodeToClipboard, 7 | getColorsValue, 8 | inputEventListner, 9 | removeColorPicker, 10 | setGradientDegreeValue, 11 | showPopup, 12 | triggerEmptyAnimation, 13 | whatColorButtonShouldShow, 14 | } from '../lib/packages/utils'; 15 | import { 16 | getAllInputElements, 17 | getCopyCodeButton, 18 | getCssOrTailwindButton, 19 | getCssOrTailwindDropdown, 20 | getDegreeSpanElement, 21 | getGradientPreview, 22 | getNewColorButton, 23 | getOpenSideBarButton, 24 | getOutput, 25 | getRange, 26 | getRemoveNewColorButton, 27 | getResetButton, 28 | getResultPage, 29 | getTailwindButton, 30 | } from '../lib/getElements'; 31 | 32 | import {deleteQueryParam} from '../lib/packages/helpers'; 33 | 34 | type Values = { 35 | degree: string; 36 | }; 37 | 38 | const attribute = 'gradient-background'; 39 | 40 | const getNewColorButtonElement = getNewColorButton(attribute); 41 | const getRemoveColorButtonElement = getRemoveNewColorButton(attribute); 42 | const getResultBtn = document.getElementById('getResultBtn'); 43 | let gradientBackgroundInputs = getAllInputElements('gradient-background'); 44 | 45 | const getDegreeElement = getRange(attribute); 46 | const resetButton = getResetButton(attribute); 47 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 48 | const showCopyClass = 'show-css-tailwind'; 49 | 50 | export function gradientBackgroundGenerator( 51 | type: 'newResults' | 'oldResults' | null 52 | ) { 53 | if (type === null) return; 54 | // Show error when the colors are not entered. 55 | const element = gradientBackgroundInputs[0]; 56 | const value = element.value; 57 | if (value.length < 3) { 58 | gradientBackgroundInputs.forEach((ele) => { 59 | if (getResultBtn) { 60 | getResultBtn.style.backgroundColor = 'grey'; 61 | } 62 | getOpenSideBarButton().style.display = 'none'; 63 | triggerEmptyAnimation(ele); 64 | }); 65 | return; 66 | } else { 67 | if (getResultBtn) { 68 | getResultBtn.style.backgroundColor = 'blue'; 69 | } 70 | } 71 | 72 | const getOutputElement = getOutput(attribute); 73 | const resultPage = getResultPage(); 74 | 75 | resultPage.style.display = 'flex'; 76 | if (type === 'oldResults') return; 77 | 78 | const values: Values = { 79 | degree: getDegreeElement.value, 80 | }; 81 | getGradientBackgroundResult(attribute, values, getOutputElement); 82 | } 83 | 84 | export function addGradientBackgroundListener() { 85 | whatColorButtonShouldShow(attribute); 86 | getNewColorButtonElement.addEventListener('click', () => { 87 | addNewColorPicker(attribute); 88 | addEventListenerToTheNewColorPicker(attribute); 89 | }); 90 | 91 | getRemoveColorButtonElement.addEventListener('click', () => { 92 | removeColorPicker(attribute); 93 | addEventListenerToTheNewColorPicker(attribute); 94 | }); 95 | 96 | inputEventListner(attribute); 97 | setGradientDegreeValue(getDegreeElement); 98 | } 99 | 100 | function copyHandler() { 101 | const outputElement = getOutput(attribute); 102 | copyCSSCodeToClipboard(attribute, outputElement); 103 | showPopup( 104 | 'Code Copied', 105 | 'Code has been successfully copied to clipboard', 106 | 'success' 107 | ); 108 | } 109 | 110 | function getCssOrTailwind(e?: MouseEvent): void { 111 | e?.stopPropagation(); 112 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 113 | } 114 | 115 | /** 116 | * sets the result to the output element 117 | * 118 | * @param attribute attribute name of the generator 119 | * @param values object that contains all values entered by users 120 | * @param outputElement output element to display result 121 | */ 122 | function getGradientBackgroundResult( 123 | attribute: string, 124 | values: Values, 125 | outputElement: HTMLElement 126 | ): void { 127 | outputElement.style.background = `linear-gradient(${ 128 | values.degree 129 | }deg, ${getColorsValue(attribute).join(', ')})`; 130 | 131 | const getCodeButtonElement = getCopyCodeButton(attribute); 132 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 133 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 134 | 135 | getCodeButtonElement.addEventListener('click', copyHandler); 136 | getTailwindCodeButtonElement.addEventListener('click', tailwindHandler); 137 | 138 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 139 | } 140 | 141 | // reset the values of all target fields 142 | resetButton.addEventListener('click', () => { 143 | const colorInput: HTMLInputElement[] = [...new Set([])]; 144 | resetButton.classList.remove('reset-show'); 145 | getDegreeSpanElement(attribute).innerHTML = 'deg'; 146 | 147 | getGradientPreview(attribute).style.background = ''; 148 | 149 | gradientBackgroundInputs.forEach((input) => { 150 | input.value = input.defaultValue; 151 | 152 | if (input.id.includes('color')) { 153 | colorInput.push(input); 154 | } 155 | }); 156 | 157 | if (colorInput.length > 2) { 158 | for (let i = 2; i < colorInput.length; i++) { 159 | removeColorPicker(attribute); 160 | } 161 | } 162 | 163 | deleteQueryParam('values'); 164 | }); 165 | 166 | // get values from all targets to get notified when values change. 167 | function getValues() { 168 | gradientBackgroundInputs.forEach((input) => { 169 | input.addEventListener('input', () => { 170 | if (input.value === '') return; 171 | 172 | if (resetButton.classList.contains('reset-show')) return; 173 | resetButton.classList.add('reset-show'); 174 | }); 175 | }); 176 | } 177 | 178 | // Tailwind codecopy handler 179 | function tailwindHandler() { 180 | const outputElement = getOutput(attribute); 181 | copyTailwindCodeToClipboard(attribute, outputElement); 182 | 183 | showPopup( 184 | 'Tailwind Code Copied', 185 | 'Code has been successfully copied to clipboard', 186 | 'success' 187 | ); 188 | } 189 | 190 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 191 | 192 | getValues(); 193 | -------------------------------------------------------------------------------- /src/pages/gradient-border.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addEventListenerToTheNewColorPicker, 3 | addNewColorPicker, 4 | addRule, 5 | closeDropdown, 6 | copyCSSCodeToClipboard, 7 | copyTailwindCodeToClipboard, 8 | getColorsValue, 9 | inputEventListner, 10 | removeColorPicker, 11 | setGradientDegreeValue, 12 | showPopup, 13 | triggerEmptyAnimation, 14 | whatColorButtonShouldShow, 15 | } from '../lib/packages/utils'; 16 | import { 17 | getAllInputElements, 18 | getCheckbox, 19 | getCopyCodeButton, 20 | getCssOrTailwindButton, 21 | getCssOrTailwindDropdown, 22 | getDegreeSpanElement, 23 | getGradientPreview, 24 | getNewColorButton, 25 | getOutput, 26 | getRadiusInput, 27 | getRange, 28 | getRemoveNewColorButton, 29 | getResetButton, 30 | getResultButton, 31 | getResultPage, 32 | getTailwindButton, 33 | } from '../lib/getElements'; 34 | 35 | type Values = { 36 | degree: string; 37 | radius: string; 38 | }; 39 | 40 | const attribute = 'gradient-border'; 41 | 42 | const getNewColorButtonElement = getNewColorButton(attribute); 43 | const getRemoveColorButtonElement = getRemoveNewColorButton(attribute); 44 | 45 | const getBorderRadiusInput = getRadiusInput(attribute); 46 | const toggleRadiusInputForGradientBorder = getCheckbox(attribute); 47 | 48 | const getDegreeElement = getRange(attribute); 49 | const resetButton = getResetButton(attribute); 50 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 51 | const showCopyClass = 'show-css-tailwind'; 52 | 53 | let gradientBorderInputs = getAllInputElements('gradient-border'); 54 | 55 | function copyHandler() { 56 | const outputElement = getOutput(attribute); 57 | copyCSSCodeToClipboard(attribute, outputElement); 58 | showPopup( 59 | 'Code Copied', 60 | 'Code has been successfully copied to clipboard', 61 | 'success' 62 | ); 63 | } 64 | 65 | function getCssOrTailwind(e?: MouseEvent): void { 66 | e?.stopPropagation(); 67 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 68 | } 69 | 70 | // closes css and tailwind dropdown on outside click 71 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 72 | 73 | /** 74 | * sets the result to the output element 75 | * 76 | * @param attribute attribute name of the generator 77 | * @param values values entered by users 78 | * @param outputElement output element to display result 79 | */ 80 | function getGradientBorderResult( 81 | attribute: string, 82 | values: Values, 83 | outputElement: HTMLElement 84 | ): void { 85 | addRule('.gradient-border::before', { 86 | background: `linear-gradient(${values.degree}deg, ${getColorsValue( 87 | attribute 88 | ).join(', ')})`, 89 | 'background-clip': 'border-box', 90 | 'border-radius': `${values.radius}px`, 91 | }); 92 | 93 | outputElement.style.backgroundColor = 'transparent'; 94 | outputElement.style.visibility = 'visible'; 95 | 96 | const getCodeButtonElement = getCopyCodeButton(attribute); 97 | getCodeButtonElement.addEventListener('click', copyHandler); 98 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 99 | getTailwindCodeButtonElement.addEventListener('click', tailwindHandler); 100 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 101 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 102 | } 103 | 104 | export function gradientBorderGenerator( 105 | type: 'newResults' | 'oldResults' | null 106 | ): void { 107 | if (type === null) return; 108 | 109 | const resultBtn = getResultButton(attribute); 110 | 111 | const item1 = gradientBorderInputs[0]; 112 | const val1 = item1.value.length; 113 | 114 | const item2 = gradientBorderInputs[1]; 115 | const val2 = item2.value.length; 116 | 117 | //Show error if input values are empty 118 | if ((resultBtn && val1 < 1) || val2 < 1) { 119 | gradientBorderInputs.forEach((e) => { 120 | if (e.value.length === 0) { 121 | triggerEmptyAnimation(e); 122 | } 123 | resultBtn.style.backgroundColor = 'grey'; 124 | }); 125 | return; 126 | } else { 127 | if (resultBtn) { 128 | resultBtn.style.backgroundColor = 'blue'; 129 | } 130 | } 131 | 132 | const getOutputElement = getOutput(attribute); 133 | const resultPage = getResultPage(); 134 | 135 | resultPage.style.display = 'flex'; 136 | if (type === 'oldResults') return; 137 | 138 | if (!toggleRadiusInputForGradientBorder.checked) { 139 | getBorderRadiusInput.value = '0'; 140 | } 141 | 142 | const values: Values = { 143 | degree: getDegreeElement.value, 144 | radius: getBorderRadiusInput.value, 145 | }; 146 | getGradientBorderResult(attribute, values, getOutputElement); 147 | } 148 | 149 | export function addGradientBorderListener() { 150 | whatColorButtonShouldShow(attribute); 151 | toggleRadiusInputForGradientBorder.addEventListener('input', function () { 152 | getBorderRadiusInput.style.display = this.checked ? 'inline' : 'none'; 153 | }); 154 | 155 | getNewColorButtonElement.addEventListener('click', () => { 156 | addNewColorPicker(attribute); 157 | addEventListenerToTheNewColorPicker(attribute); 158 | }); 159 | 160 | getRemoveColorButtonElement.addEventListener('click', () => { 161 | removeColorPicker(attribute); 162 | addEventListenerToTheNewColorPicker(attribute); 163 | }); 164 | 165 | inputEventListner(attribute); 166 | 167 | setGradientDegreeValue(getDegreeElement); 168 | } 169 | 170 | // reset the values of all target fields 171 | resetButton.addEventListener('click', () => { 172 | const colorInput: HTMLInputElement[] = [...new Set([])]; 173 | resetButton.classList.remove('reset-show'); 174 | getDegreeSpanElement(attribute).innerHTML = 'deg'; 175 | 176 | getGradientPreview(attribute).style.background = ''; 177 | 178 | gradientBorderInputs.forEach((input) => { 179 | input.checked = false; 180 | input.value = input.defaultValue; 181 | 182 | if (input.id.includes('color')) { 183 | colorInput.push(input); 184 | } 185 | }); 186 | 187 | if (colorInput.length > 2) { 188 | for (let i = 2; i < colorInput.length; i++) { 189 | removeColorPicker(attribute); 190 | } 191 | } 192 | }); 193 | 194 | // get values from all targets to get notified when values change. 195 | 196 | function getValues() { 197 | gradientBorderInputs.forEach((input) => { 198 | input.addEventListener('input', () => { 199 | if (input.nodeName === 'TEXTAREA') { 200 | if (input.value === '') return; 201 | } 202 | 203 | if (resetButton.classList.contains('reset-show')) return; 204 | resetButton.classList.add('reset-show'); 205 | }); 206 | }); 207 | } 208 | getValues(); 209 | 210 | // Tailwind codecopy handler 211 | function tailwindHandler() { 212 | copyTailwindCodeToClipboard(attribute); 213 | showPopup( 214 | 'Tailwind Code Copied', 215 | 'Code has been successfully copied to clipboard', 216 | 'success' 217 | ); 218 | } 219 | -------------------------------------------------------------------------------- /src/pages/gradient-text.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addEventListenerToTheNewColorPicker, 3 | addNewColorPicker, 4 | closeDropdown, 5 | copyCSSCodeToClipboard, 6 | copyTailwindCodeToClipboard, 7 | downloadPNG, 8 | downloadSVG, 9 | getColorsValue, 10 | inputEventListner, 11 | removeColorPicker, 12 | setGradientDegreeValue, 13 | showPopup, 14 | triggerEmptyAnimation, 15 | whatColorButtonShouldShow, 16 | } from '../lib/packages/utils'; 17 | import { 18 | getAllInputElements, 19 | getCopyCodeButton, 20 | getCssOrTailwindButton, 21 | getCssOrTailwindDropdown, 22 | getDegreeSpanElement, 23 | getGradientPreview, 24 | getInputText, 25 | getNewColorButton, 26 | getOpenSideBarButton, 27 | getOutput, 28 | getPNGButton, 29 | getPngOrSvgButton, 30 | getPngOrSvgDropdown, 31 | getRange, 32 | getRemoveNewColorButton, 33 | getResetButton, 34 | getResultPage, 35 | getSVGButton, 36 | getTailwindButton, 37 | } from '../lib/getElements'; 38 | 39 | type Values = { 40 | degree: string; 41 | }; 42 | 43 | const attribute = 'gradient-text'; 44 | 45 | const gradientTextInputs = getAllInputElements(attribute); 46 | 47 | const getNewColorButtonElement = getNewColorButton(attribute); 48 | const getRemoveColorButtonElement = getRemoveNewColorButton(attribute); 49 | 50 | const getDegreeElement = getRange(attribute); 51 | const resetButton = getResetButton(attribute); 52 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 53 | const getPngOrSvgDropdownElement = getPngOrSvgDropdown(attribute); 54 | const showCopyClass = 'show-css-tailwind'; 55 | const showPngOrSvgClass = 'show-png-svg'; 56 | 57 | function copyHandler() { 58 | const outputElement = getOutput(attribute); 59 | copyCSSCodeToClipboard(attribute, outputElement); 60 | showPopup( 61 | 'Code Copied', 62 | 'Code has been successfully copied to clipboard', 63 | 'success' 64 | ); 65 | } 66 | 67 | function getCssOrTailwind(e?: MouseEvent): void { 68 | e?.stopPropagation(); 69 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 70 | } 71 | 72 | function getPngOrSvg(e?: MouseEvent) { 73 | e?.stopPropagation(); 74 | getPngOrSvgDropdownElement.classList.toggle(showPngOrSvgClass); 75 | } 76 | 77 | // closes css and tailwind dropdown on outside click 78 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 79 | 80 | // closes png and css dropdown outside click 81 | closeDropdown(getPngOrSvg, getPngOrSvgDropdownElement, showPngOrSvgClass); 82 | 83 | function pngDownloadHandler() { 84 | const outputElement = getOutput(attribute); 85 | downloadPNG(attribute, outputElement); 86 | } 87 | 88 | function svgDownloadHanlder() { 89 | const outputElement = getOutput(attribute); 90 | downloadSVG(attribute, outputElement); 91 | } 92 | 93 | export function gradientTextGenerator( 94 | type: 'newResults' | 'oldResults' | null 95 | ): void { 96 | if (type === null) return; 97 | 98 | const getInputElement = getInputText(attribute); 99 | 100 | if (getInputElement.value.length === 0) { 101 | getOpenSideBarButton().style.display = 'none'; 102 | triggerEmptyAnimation(getInputElement); 103 | return; 104 | } 105 | 106 | const getOutputElement = getOutput(attribute); 107 | const resultPage = getResultPage(); 108 | 109 | resultPage.style.display = 'flex'; 110 | if (getOutputElement === null || type === 'oldResults') return; 111 | getOutputElement.style.display = 'grid'; 112 | getOutputElement.style.placeItems = 'center'; 113 | 114 | const values = { 115 | degree: getDegreeElement.value, 116 | }; 117 | 118 | getGradientTextResult( 119 | attribute, 120 | getInputElement.value, 121 | values, 122 | getOutputElement 123 | ); 124 | } 125 | 126 | /** 127 | * creates the text, adds styling and shows the new text 128 | * 129 | * @param text Text to add the gradient 130 | * @param outputElement Elements that shows the result 131 | * @param values object of all values inputted by user 132 | * @param outputElement Output element for displaying the result 133 | */ 134 | function getGradientTextResult( 135 | attribute: string, 136 | text: string, 137 | values: Values, 138 | outputElement: HTMLElement 139 | ): void { 140 | const createTextElement = () => { 141 | const wordElement = document.createElement('p'); 142 | wordElement.innerText = text; 143 | wordElement.style.fontSize = '2rem'; 144 | wordElement.style.background = `linear-gradient(${ 145 | values.degree 146 | }deg, ${getColorsValue(attribute).join(', ')})`; 147 | wordElement.style.backgroundClip = 'text'; 148 | wordElement.style.webkitBackgroundClip = 'text'; 149 | wordElement.style.webkitTextFillColor = 'transparent'; 150 | 151 | return wordElement; 152 | }; 153 | 154 | const getCodeButtonElement = getCopyCodeButton(attribute); 155 | const getPNGButtonElement = getPNGButton(attribute); 156 | const getSVGButtonElement = getSVGButton(attribute); 157 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 158 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 159 | const getPngOrSvgButtonElement = getPngOrSvgButton(attribute); 160 | 161 | if (outputElement.childElementCount >= 1) { 162 | outputElement.innerHTML = ''; 163 | outputElement.appendChild(createTextElement()); 164 | } else { 165 | outputElement.appendChild(createTextElement()); 166 | } 167 | 168 | getPNGButtonElement.addEventListener('click', pngDownloadHandler); 169 | 170 | getSVGButtonElement.addEventListener('click', svgDownloadHanlder); 171 | 172 | getCodeButtonElement.addEventListener('click', copyHandler); 173 | 174 | getTailwindCodeButtonElement.addEventListener('click', tailwindHandler); 175 | 176 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 177 | 178 | getPngOrSvgButtonElement.addEventListener('click', getPngOrSvg); 179 | } 180 | 181 | export function addGradientTextListener() { 182 | whatColorButtonShouldShow(attribute); 183 | 184 | getNewColorButtonElement.addEventListener('click', () => { 185 | addNewColorPicker(attribute); 186 | addEventListenerToTheNewColorPicker(attribute); 187 | }); 188 | 189 | getRemoveColorButtonElement.addEventListener('click', () => { 190 | removeColorPicker(attribute); 191 | addEventListenerToTheNewColorPicker(attribute); 192 | }); 193 | 194 | inputEventListner(attribute); 195 | 196 | setGradientDegreeValue(getDegreeElement); 197 | } 198 | 199 | // reset the values of all target fields 200 | 201 | resetButton.addEventListener('click', () => { 202 | const colorInput: HTMLInputElement[] = [...new Set([])]; 203 | resetButton.classList.remove('reset-show'); 204 | getDegreeSpanElement(attribute).innerHTML = 'deg'; 205 | 206 | getGradientPreview(attribute).style.background = ''; 207 | 208 | gradientTextInputs.forEach((input) => { 209 | input.value = input.defaultValue; 210 | 211 | if (input.id.includes('color')) { 212 | colorInput.push(input); 213 | } 214 | }); 215 | 216 | if (colorInput.length > 2) { 217 | for (let i = 2; i < colorInput.length; i++) { 218 | removeColorPicker(attribute); 219 | } 220 | } 221 | }); 222 | 223 | // get values from all targets to get notified when values change. 224 | 225 | function getValues() { 226 | gradientTextInputs.forEach((input) => { 227 | input.addEventListener('input', () => { 228 | if (input.nodeName === 'TEXTAREA') { 229 | if (input.value === '') return; 230 | } 231 | 232 | if (resetButton.classList.contains('reset-show')) return; 233 | resetButton.classList.add('reset-show'); 234 | }); 235 | }); 236 | } 237 | getValues(); 238 | 239 | // Tailwind codecopy handler 240 | function tailwindHandler() { 241 | const outputElement = getOutput(attribute); 242 | copyTailwindCodeToClipboard(attribute, outputElement); 243 | showPopup( 244 | 'Tailwind Code Copied', 245 | 'Code has been successfully copied to clipboard', 246 | 'success' 247 | ); 248 | } 249 | -------------------------------------------------------------------------------- /src/pages/grid-generator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | closeDropdown, 3 | copyCSSCodeToClipboard, 4 | copyTailwindCodeToClipboard, 5 | showPopup, 6 | } from '../lib/packages/utils'; 7 | import { 8 | getAllFields, 9 | getColumnGap, 10 | getCopyCodeButton, 11 | getCssOrTailwindButton, 12 | getCssOrTailwindDropdown, 13 | getGridFields, 14 | getGridPreview, 15 | getNumberOfColumns, 16 | getNumberOfRows, 17 | getResetButton, 18 | getRowGap, 19 | getTailwindButton, 20 | } from '../lib/getElements'; 21 | 22 | import {setQueryParam} from '../lib/packages/helpers'; 23 | 24 | const attribute = 'grid-generators'; 25 | const showCopyClass = 'show-css-tailwind'; 26 | 27 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 28 | const preview = getGridPreview(attribute); 29 | const noOfColumns = getNumberOfColumns(attribute); 30 | const noOfRows = getNumberOfRows(attribute); 31 | const rowGapValue = getRowGap(attribute); 32 | const columnGapValue = getColumnGap(attribute); 33 | 34 | function areInputsValid() { 35 | const noOfColumns = getNumberOfColumns(attribute); 36 | const noOfRows = getNumberOfRows(attribute); 37 | 38 | const isColumnInputValid = 39 | noOfColumns.value.length > 0 40 | ? parseInt(noOfColumns.value) >= 1 && parseInt(noOfColumns.value) <= 100 41 | : true; 42 | const isRowInputValid = 43 | noOfRows.value.length > 0 44 | ? parseInt(noOfRows.value) >= 1 && parseInt(noOfRows.value) <= 100 45 | : true; 46 | 47 | if (isColumnInputValid && isRowInputValid) return true; 48 | 49 | showPopup( 50 | 'Limit Exceeded', 51 | 'The input value should be within 1 and 100.', 52 | 'error' 53 | ); 54 | 55 | return false; 56 | } 57 | 58 | export function gridGenerator(): void { 59 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 60 | 61 | const allGridInputs = [noOfColumns, noOfRows, rowGapValue, columnGapValue]; 62 | 63 | const allGridInputFields = getGridFields(attribute, [ 64 | 'rows', 65 | 'columns', 66 | 'row-gaps', 67 | 'column-gaps', 68 | ]); 69 | 70 | const getGridColValue = () => `repeat(${parseInt(noOfColumns.value)}, 1fr)`; 71 | const getGridRowValue = () => `repeat(${parseInt(noOfRows.value)}, 1fr)`; 72 | const getGridColGapValue = () => `${parseInt(columnGapValue.value)}px`; 73 | const getGridRowGapValue = () => `${parseInt(rowGapValue.value)}px`; 74 | 75 | allGridInputs.forEach((input, index) => { 76 | if (index < 4) { 77 | allGridInputFields[index].textContent = `${input.value}px`; 78 | } 79 | 80 | input.addEventListener('input', () => { 81 | if (areInputsValid() === false) return; 82 | preview.style.display = 'grid'; 83 | preview.style.gridTemplateColumns = getGridColValue(); 84 | preview.style.gridTemplateRows = getGridRowValue(); 85 | preview.style.rowGap = getGridRowGapValue(); 86 | preview.style.columnGap = getGridColGapValue(); 87 | 88 | const values = { 89 | columns: getGridColValue(), 90 | rows: getGridRowValue(), 91 | rowGap: getGridRowGapValue(), 92 | columnGap: getGridColGapValue(), 93 | }; 94 | 95 | setQueryParam('values', JSON.stringify(values)); 96 | updatePreviewElement(); 97 | }); 98 | }); 99 | 100 | const getCSSCodeButtonElement = getCopyCodeButton(attribute); 101 | getCSSCodeButtonElement.addEventListener('click', copyCSSHandler); 102 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 103 | getTailwindCodeButtonElement.addEventListener('click', copyTailwindHandler); 104 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 105 | } 106 | 107 | function getCssOrTailwind(e?: MouseEvent): void { 108 | e?.stopPropagation(); 109 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 110 | } 111 | 112 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 113 | 114 | function doInputExist() { 115 | const noOfColumns = getNumberOfColumns(attribute); 116 | const noOfRows = getNumberOfRows(attribute); 117 | 118 | if (!noOfColumns.value || !noOfRows.value) { 119 | showPopup("Couldn't Copy Code", 'Some input value may be missing', 'error'); 120 | return false; 121 | } 122 | 123 | return true; 124 | } 125 | 126 | function copyTailwindHandler() { 127 | const outputElement: HTMLElement = getGridPreview(attribute); 128 | if (doInputExist() === false) return; 129 | copyTailwindCodeToClipboard(attribute, outputElement); 130 | showPopup( 131 | 'Tailwind Code Copied', 132 | 'Code has been successfully copied to clipboard', 133 | 'success' 134 | ); 135 | } 136 | 137 | function copyCSSHandler() { 138 | const outputElement = getGridPreview(attribute); 139 | if (doInputExist() === false) return; 140 | copyCSSCodeToClipboard(attribute, outputElement); 141 | showPopup( 142 | 'Code Copied', 143 | 'Code has been successfully copied to clipboard', 144 | 'success' 145 | ); 146 | } 147 | 148 | function resetValues() { 149 | const {inputs} = getAllFields(attribute); 150 | const preview = getGridPreview(attribute); 151 | 152 | getResetButton(attribute).addEventListener('click', () => { 153 | inputs.forEach((input) => { 154 | input.value = input.defaultValue; 155 | }); 156 | updatePreviewElement(); 157 | preview.setAttribute('style', ''); 158 | getResetButton(attribute).classList.remove('reset-show'); 159 | }); 160 | } 161 | 162 | function getValues() { 163 | const {inputs} = getAllFields(attribute); 164 | 165 | inputs.forEach((input) => { 166 | input.addEventListener('input', () => { 167 | getResetButton(attribute).classList.add('reset-show'); 168 | resetValues(); 169 | }); 170 | }); 171 | } 172 | 173 | getValues(); 174 | 175 | function updatePreviewElement() { 176 | const preview = getGridPreview(attribute); 177 | preview.innerHTML = ''; 178 | const noOfColumns = getNumberOfColumns(attribute).value; 179 | const noOfRows = getNumberOfRows(attribute).value; 180 | const rows = parseInt(noOfRows !== '' ? noOfRows : '0'); 181 | const columns = parseInt(noOfColumns !== '' ? noOfColumns : '0'); 182 | 183 | if (rows > columns) { 184 | for (let i = 0; i < rows; i++) { 185 | for (let j = 0; j < columns; j++) { 186 | const child = document.createElement('div'); 187 | child.style.border = '1px solid white'; 188 | preview.appendChild(child); 189 | } 190 | } 191 | } else { 192 | for (let i = 0; i < columns; i++) { 193 | for (let j = 0; j < rows; j++) { 194 | const child = document.createElement('div'); 195 | child.style.border = '1px solid white'; 196 | preview.appendChild(child); 197 | } 198 | } 199 | } 200 | } 201 | 202 | export const applyGridValues = (values: string) => { 203 | const parsed = JSON.parse(values); 204 | 205 | noOfColumns.value = getFirstValue(parsed.columns); 206 | noOfRows.value = getFirstValue(parsed.rows); 207 | rowGapValue.value = parsed.rowGap[0]; 208 | columnGapValue.value = parsed.columnGap[0]; 209 | 210 | preview.style.display = 'grid'; 211 | preview.style.gridTemplateColumns = parsed.columns; 212 | preview.style.gridTemplateRows = parsed.rows; 213 | preview.style.rowGap = parsed.rowGap; 214 | preview.style.columnGap = parsed.columnGap; 215 | 216 | updatePreviewElement(); 217 | }; 218 | 219 | function getFirstValue(input: string) { 220 | const match = input.match(/repeat\((\d+),\s*([^\)]+)\)/); 221 | if (match) { 222 | return match[1]; 223 | } else { 224 | return ''; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/pages/input-range.ts: -------------------------------------------------------------------------------- 1 | import { 2 | closeDropdown, 3 | copyCSSCodeToClipboard, 4 | copyTailwindCodeToClipboard, 5 | showPopup, 6 | } from '../lib/packages/utils'; 7 | import { 8 | getAllFields, 9 | getAllInputElements, 10 | getCheckbox, 11 | getColorInput1, 12 | getColorInput2, 13 | getCopyCodeButton, 14 | getCssOrTailwindButton, 15 | getCssOrTailwindDropdown, 16 | getRadiusInput, 17 | getResetButton, 18 | getTailwindButton, 19 | } from '../lib/getElements'; 20 | 21 | import {setQueryParam} from '../lib/packages/helpers'; 22 | 23 | type RangeType = 'track' | 'thumb'; 24 | type RangeValues = { 25 | height: string; 26 | width: string; 27 | radius: string | number; 28 | color: string; 29 | }; 30 | type Values = { 31 | thumb: RangeValues; 32 | track: RangeValues; 33 | }; 34 | 35 | const attribute = 'input-range'; 36 | const showCopyClass = 'show-css-tailwind'; 37 | 38 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 39 | const getCodeButton = getCopyCodeButton(attribute); 40 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 41 | const getTrackColor = getColorInput1(attribute); 42 | const getThumbColor = getColorInput2(attribute); 43 | 44 | const trackCheckBox = getCheckbox(`${attribute}-track`); 45 | const thumbCheckBox = getCheckbox(`${attribute}-thumb`); 46 | const getTrackRadius = getRadiusInput(`${attribute}-track`); 47 | const getThumbRadius = getRadiusInput(`${attribute}-thumb`); 48 | 49 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 50 | const getTrackHeightElement = document.getElementById( 51 | 'track-height' 52 | ) as HTMLInputElement; 53 | const getThumbHeightElement = document.getElementById( 54 | 'thumb-height' 55 | ) as HTMLInputElement; 56 | const getTrackWidthElement = document.getElementById( 57 | 'track-width' 58 | ) as HTMLInputElement; 59 | const getThumbWidthElement = document.getElementById( 60 | 'thumb-width' 61 | ) as HTMLInputElement; 62 | 63 | function setLabelValue() { 64 | const getThumbHeightLabel = document.getElementById( 65 | 'thumb-height-label' 66 | ) as HTMLElement; 67 | const getThumbHeightElement = document.getElementById( 68 | 'thumb-height' 69 | ) as HTMLInputElement; 70 | 71 | const getThumbWidthLabel = document.getElementById( 72 | 'thumb-width-label' 73 | ) as HTMLElement; 74 | const getThumbWidthElement = document.getElementById( 75 | 'thumb-width' 76 | ) as HTMLInputElement; 77 | 78 | const getTrackHeightLabel = document.getElementById( 79 | 'track-height-label' 80 | ) as HTMLElement; 81 | const getTrackHeightElement = document.getElementById( 82 | 'track-height' 83 | ) as HTMLInputElement; 84 | 85 | const getTrackWidthLabel = document.getElementById( 86 | 'track-width-label' 87 | ) as HTMLElement; 88 | const getTrackWidthElement = document.getElementById( 89 | 'track-width' 90 | ) as HTMLInputElement; 91 | 92 | getThumbHeightLabel.innerText = `${getThumbHeightElement.value}px`; 93 | 94 | getThumbWidthLabel.innerText = `${getThumbWidthElement.value}px`; 95 | 96 | getTrackHeightLabel.innerText = `${getTrackHeightElement.value}px`; 97 | 98 | getTrackWidthLabel.innerText = `${getTrackWidthElement.value}px`; 99 | } 100 | 101 | function setBorderRadiusValue(element: RangeType) { 102 | const range = `${attribute}-${element}`; 103 | const getBorderRadiusInput = getRadiusInput(range); 104 | 105 | if (getCheckbox(range).checked) { 106 | getRadiusInput(range).value = getBorderRadiusInput.value; 107 | } else { 108 | getRadiusInput(range).value = '10'; 109 | } 110 | } 111 | 112 | function setPreview(values: Values): void { 113 | const previewElement = document.getElementById( 114 | 'preview-range' 115 | ) as HTMLInputElement; 116 | 117 | const thumbValues = values.thumb; 118 | const trackValues = values.track; 119 | 120 | // set track styles 121 | previewElement.style.setProperty( 122 | '--preview-track-height', 123 | `${trackValues.height}px` 124 | ); 125 | previewElement.style.setProperty( 126 | '--preview-track-width', 127 | `${trackValues.width}px` 128 | ); 129 | previewElement.style.setProperty('--preview-track-color', trackValues.color); 130 | previewElement.style.setProperty( 131 | '--preview-track-radius', 132 | `${trackValues.radius}px` 133 | ); 134 | 135 | // set thumb styles 136 | previewElement.style.setProperty( 137 | '--preview-thumb-height', 138 | `${thumbValues.height}px` 139 | ); 140 | 141 | previewElement.style.setProperty( 142 | '--preview-thumb-width', 143 | `${thumbValues.width}px` 144 | ); 145 | previewElement.style.setProperty('--preview-thumb-color', thumbValues.color); 146 | previewElement.style.setProperty( 147 | '--preview-thumb-radius', 148 | `${thumbValues.radius}px` 149 | ); 150 | } 151 | 152 | function doInputsExist() { 153 | const getTrackColor = getColorInput1(attribute); 154 | const getThumbColor = getColorInput2(attribute); 155 | 156 | const doColorInputsExist = getTrackColor.value && getThumbColor.value; 157 | 158 | const trackCheckBox = getCheckbox(`${attribute}-track`); 159 | const getTrackRadius = getRadiusInput(`${attribute}-track`); 160 | 161 | const doesTrackRadiusInputExists = trackCheckBox.checked 162 | ? getTrackRadius.value !== '' 163 | : true; 164 | 165 | const thumbCheckBox = getCheckbox(`${attribute}-thumb`); 166 | const getThumbRadius = getRadiusInput(`${attribute}-thumb`); 167 | 168 | const doesThumbRadiusInputExists = thumbCheckBox.checked 169 | ? getThumbRadius.value !== '' 170 | : true; 171 | 172 | if ( 173 | doColorInputsExist && 174 | doesTrackRadiusInputExists && 175 | doesThumbRadiusInputExists 176 | ) 177 | return true; 178 | 179 | showPopup("Couldn't Copy Code", 'Some input value may be missing', 'error'); 180 | return false; 181 | } 182 | 183 | function copyHandler() { 184 | const previewElement = document.getElementById( 185 | 'preview-range' 186 | ) as HTMLInputElement; 187 | if (doInputsExist() === false) return; 188 | copyCSSCodeToClipboard(attribute, previewElement); 189 | showPopup( 190 | 'Code Copied', 191 | 'Code has been successfully copied to clipboard', 192 | 'success' 193 | ); 194 | } 195 | function getCssOrTailwind(e?: MouseEvent): void { 196 | e?.stopPropagation(); 197 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 198 | } 199 | 200 | // closes css and tailwind dropdown on outside click 201 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 202 | 203 | export const rangeGenerator = () => { 204 | const allRangeInputElements = getAllInputElements(attribute); 205 | 206 | allRangeInputElements.forEach((item) => { 207 | item.addEventListener('input', () => { 208 | const values = { 209 | thumb: { 210 | height: getThumbHeightElement.value, 211 | width: getThumbWidthElement.value, 212 | radius: thumbCheckBox.checked ? getThumbRadius.value : 0, 213 | color: getThumbColor.value, 214 | }, 215 | track: { 216 | height: getTrackHeightElement.value, 217 | width: getTrackWidthElement.value, 218 | radius: trackCheckBox.checked ? getTrackRadius.value : 0, 219 | color: getTrackColor.value, 220 | }, 221 | }; 222 | setQueryParam('values', JSON.stringify(values)); 223 | setPreview(values); 224 | setLabelValue(); 225 | }); 226 | }); 227 | 228 | trackCheckBox.addEventListener('input', () => { 229 | setBorderRadiusValue('track'); 230 | getTrackRadius.style.display = trackCheckBox.checked ? 'inline' : 'none'; 231 | }); 232 | thumbCheckBox.addEventListener('input', () => { 233 | setBorderRadiusValue('thumb'); 234 | getThumbRadius.style.display = thumbCheckBox.checked ? 'inline' : 'none'; 235 | }); 236 | 237 | getCodeButton.addEventListener('click', copyHandler); 238 | getTailwindCodeButtonElement.addEventListener('click', tailwindHandler); 239 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 240 | }; 241 | 242 | function resetValues() { 243 | const {inputs} = getAllFields(attribute); 244 | 245 | getResetButton(attribute).addEventListener('click', () => { 246 | inputs.forEach((input) => { 247 | input.value = input.defaultValue; 248 | input.checked = input.defaultChecked; 249 | }); 250 | 251 | ( 252 | document.querySelector( 253 | '[data-content="input-range"] #thumb-height-label' 254 | ) as HTMLElement 255 | ).innerHTML = ''; 256 | ( 257 | document.querySelector( 258 | '[data-content="input-range"] #track-height-label' 259 | ) as HTMLElement 260 | ).innerHTML = ''; 261 | ( 262 | document.querySelector( 263 | '[data-content="input-range"] #thumb-width-label' 264 | ) as HTMLElement 265 | ).innerHTML = ''; 266 | ( 267 | document.querySelector( 268 | '[data-content="input-range"] #track-width-label' 269 | ) as HTMLElement 270 | ).innerHTML = ''; 271 | 272 | getResetButton(attribute).classList.remove('reset-show'); 273 | }); 274 | } 275 | 276 | // get values from all targets to get notified when values change. 277 | function getValues() { 278 | const {inputs} = getAllFields(attribute); 279 | 280 | inputs.forEach((input) => { 281 | input.addEventListener('input', () => { 282 | getResetButton(attribute).classList.add('reset-show'); 283 | resetValues(); 284 | }); 285 | }); 286 | } 287 | getValues(); 288 | 289 | // Tailwind codecopy handler 290 | function tailwindHandler() { 291 | const element = document.querySelector('#preview-range') as HTMLInputElement; 292 | if (doInputsExist() === false) return; 293 | copyTailwindCodeToClipboard(attribute, element); 294 | showPopup( 295 | 'Tailwind Code Copied', 296 | 'Code has been successfully copied to clipboard', 297 | 'success' 298 | ); 299 | } 300 | 301 | export const applyInputRangeValues = (values: string) => { 302 | const parsed: Values = JSON.parse(values); 303 | 304 | getThumbHeightElement.value = parsed.thumb.height; 305 | getThumbWidthElement.value = parsed.thumb.width; 306 | getThumbColor.value = parsed.thumb.color; 307 | 308 | if (parsed.thumb.radius) { 309 | thumbCheckBox.checked = true; 310 | getThumbRadius.style.display = 'inline'; 311 | getThumbRadius.value = parsed.thumb.radius + ''; 312 | } 313 | 314 | getTrackHeightElement.value = parsed.track.height; 315 | getTrackWidthElement.value = parsed.track.width; 316 | getTrackColor.value = parsed.track.color; 317 | 318 | if (parsed.track.radius) { 319 | trackCheckBox.checked = true; 320 | getTrackRadius.style.display = 'inline'; 321 | getTrackRadius.value = parsed.track.radius + ''; 322 | } 323 | 324 | setPreview(parsed); 325 | setLabelValue(); 326 | }; 327 | -------------------------------------------------------------------------------- /src/pages/pic-text.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getOutput, 3 | getResultPage, 4 | getCopyCodeButton, 5 | getPNGButton, 6 | getSVGButton, 7 | getTailwindButton, 8 | getCssOrTailwindButton, 9 | getCssOrTailwindDropdown, 10 | getPngOrSvgButton, 11 | getPngOrSvgDropdown, 12 | } from '../lib/getElements'; 13 | import { 14 | copyCSSCodeToClipboard, 15 | showPopup, 16 | downloadPNG, 17 | downloadSVG, 18 | copyTailwindCodeToClipboard, 19 | closeDropdown, 20 | } from '../lib/packages/utils'; 21 | 22 | const attribute = 'pic-text'; 23 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 24 | const getPngOrSvgDropdownElement = getPngOrSvgDropdown(attribute); 25 | const showCopyClass = 'show-css-tailwind'; 26 | const showPngOrSvgClass = 'show-png-svg'; 27 | 28 | function copyHandler() { 29 | const outputElement = getOutput(attribute); 30 | copyCSSCodeToClipboard(attribute, outputElement); 31 | showPopup( 32 | 'Code Copied', 33 | 'Code has been successfully copied to clipboard', 34 | 'success' 35 | ); 36 | } 37 | 38 | function getCssOrTailwind(e?: MouseEvent): void { 39 | e?.stopPropagation(); 40 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 41 | } 42 | 43 | function getPngOrSvg(e?: MouseEvent) { 44 | e?.stopPropagation(); 45 | getPngOrSvgDropdownElement.classList.toggle(showPngOrSvgClass); 46 | } 47 | 48 | // closes css and tailwind dropdown on outside click 49 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 50 | 51 | // closes png and css dropdown outside click 52 | closeDropdown(getPngOrSvg, getPngOrSvgDropdownElement, showPngOrSvgClass); 53 | 54 | function pngDownloadHandler() { 55 | const outputElement = getOutput(attribute); 56 | downloadPNG(attribute, outputElement); 57 | } 58 | 59 | function svgDownloadHanlder() { 60 | const outputElement = getOutput(attribute); 61 | downloadSVG(attribute, outputElement); 62 | } 63 | 64 | /** 65 | * @param image - image inputed string 66 | * @param imageHeight - image height number 67 | */ 68 | export function picTextGenerator( 69 | image: string, 70 | imageHeight: number, 71 | type: 'newResults' | 'oldResults' | null 72 | ): void { 73 | if (type === null) return; 74 | 75 | const outputNode = getOutput(attribute); 76 | const resultPage = getResultPage(); 77 | 78 | // The value 19 is the result I got by dividing the height of the image by 79 | // the number of lines of text necessary to cover its full height. 80 | // Probably based on font size ... I'm wondering if it will work everywhere though. 81 | // It's a lame solution, we have to find a better way in the future. 82 | const imageText = '###########################'.repeat( 83 | Math.ceil(imageHeight / 19) 84 | ); 85 | 86 | resultPage.style.display = 'flex'; 87 | if (type === 'oldResults') return; 88 | outputNode.style.background = `url(${image}) center no-repeat`; 89 | outputNode.style.width = 'var(--output-width)'; 90 | outputNode.style.height = `${imageHeight}px`; 91 | outputNode.style.backgroundSize = 'var(--output-width)'; 92 | outputNode.style.backgroundClip = 'text'; 93 | outputNode.style.webkitBackgroundClip = 'text'; 94 | outputNode.style.webkitTextFillColor = 'rgba(255, 255, 255, 0.1)'; 95 | outputNode.innerText = imageText; 96 | getPicTextResult(attribute, outputNode); 97 | } 98 | 99 | // function to disable the get result button once page loads 100 | function disableImgResultBtn() { 101 | const getPicResultBtn = document.querySelector( 102 | '[data-button="pic-text"]' 103 | ) as HTMLButtonElement; 104 | 105 | getPicResultBtn.style.pointerEvents = 'none'; 106 | } 107 | 108 | // disable function instantiation 109 | disableImgResultBtn(); 110 | 111 | /** 112 | * Sets the image and the text(if any) to the output element 113 | * 114 | * @param attribute - The attribute name of the generator element 115 | * @param outputNode - The output element to display 116 | */ 117 | function getPicTextResult(attribute: string, outputNode: HTMLElement): void { 118 | if (outputNode === null) { 119 | return; 120 | } 121 | 122 | const getCodeButtonElement = getCopyCodeButton(attribute); 123 | const getPNGButtonElement = getPNGButton(attribute); 124 | const getSVGButtonElement = getSVGButton(attribute); 125 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 126 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 127 | const getPngOrSvgButtonElement = getPngOrSvgButton(attribute); 128 | 129 | getPNGButtonElement.addEventListener('click', pngDownloadHandler); 130 | getSVGButtonElement.addEventListener('click', svgDownloadHanlder); 131 | getCodeButtonElement.addEventListener('click', copyHandler); 132 | getTailwindCodeButtonElement.addEventListener('click', tailwindHandler); 133 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 134 | getPngOrSvgButtonElement.addEventListener('click', getPngOrSvg); 135 | } 136 | 137 | // Tailwind codecopy handler 138 | function tailwindHandler() { 139 | copyTailwindCodeToClipboard(attribute); 140 | showPopup( 141 | 'Tailwind Code Copied', 142 | 'Code has been successfully copied to clipboard', 143 | 'success' 144 | ); 145 | } 146 | -------------------------------------------------------------------------------- /src/pages/scroll.ts: -------------------------------------------------------------------------------- 1 | import copy from 'copy-to-clipboard'; 2 | 3 | import { 4 | getAllColorInput, 5 | getCopyCodeButton, 6 | getCssOrTailwindButton, 7 | getCssOrTailwindDropdown, 8 | getScrollPreview, 9 | } from '../lib/getElements'; 10 | import {closeDropdown, showPopup} from '../lib/packages/utils'; 11 | 12 | type Values = { 13 | trackColor: string; 14 | thumbColor: string; 15 | hoverColor: string; 16 | width: string; 17 | }; 18 | 19 | const attribute = 'scroll'; 20 | const showCopyClass = 'show-css-tailwind'; 21 | 22 | const [trackColor, thumbColor, hoverColor] = Array.from( 23 | getAllColorInput(attribute) 24 | ); 25 | const width = document.querySelector('#scroll-width') as HTMLInputElement; 26 | 27 | const getCodeButton = getCopyCodeButton(attribute); 28 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 29 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 30 | 31 | function getCssOrTailwind(e?: MouseEvent): void { 32 | e?.stopPropagation(); 33 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 34 | } 35 | 36 | // closes css and tailwind dropdown on outside click 37 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 38 | 39 | function doInputsExist() { 40 | const [trackColor, thumbColor, hoverColor] = Array.from( 41 | getAllColorInput(attribute) 42 | ); 43 | const width = document.querySelector('#scroll-width') as HTMLInputElement; 44 | 45 | const isColorFilled = 46 | trackColor.value && thumbColor.value && hoverColor.value; 47 | 48 | if (isColorFilled && width.value) return true; 49 | 50 | showPopup("Couldn't Copy Code", 'Some input value may be missing', 'error'); 51 | return false; 52 | } 53 | 54 | function copyHandler(values: Values) { 55 | if (doInputsExist() === false) return; 56 | 57 | copy(` 58 | /* width */ 59 | ::-webkit-scrollbar { 60 | width: ${values.width}px; 61 | } 62 | 63 | /* Track */ 64 | ::-webkit-scrollbar-track { 65 | box-shadow: inset 0 0 5px ${values.trackColor}; 66 | } 67 | 68 | /* Handle */ 69 | ::-webkit-scrollbar-thumb { 70 | background: ${values.thumbColor}; 71 | } 72 | 73 | /* Handle on hover */ 74 | ::-webkit-scrollbar-thumb:hover { 75 | background: ${values.hoverColor}; 76 | } 77 | `); 78 | 79 | showPopup( 80 | 'Code Copied', 81 | 'Code has been successfully copied to clipboard', 82 | 'success' 83 | ); 84 | } 85 | 86 | export function scrollGenerator() { 87 | const previewElement = getScrollPreview(); 88 | 89 | function applyPreviewStyles(values: Values) { 90 | if (!previewElement) return; 91 | 92 | // Apply styles directly to the preview element 93 | previewElement.style.setProperty('--scrollbar-width', `${values.width}px`); 94 | previewElement.style.setProperty( 95 | '--scrollbar-track-color', 96 | values.trackColor 97 | ); 98 | previewElement.style.setProperty( 99 | '--scrollbar-thumb-color', 100 | values.thumbColor 101 | ); 102 | previewElement.style.setProperty( 103 | '--scrollbar-thumb-hover-color', 104 | values.hoverColor 105 | ); 106 | 107 | const styleTag = document.createElement('style'); 108 | styleTag.id = 'scroll-preview-style'; 109 | styleTag.innerHTML = ` 110 | #scroll-preview::-webkit-scrollbar { 111 | width: var(--scrollbar-width); 112 | } 113 | #scroll-preview::-webkit-scrollbar-track { 114 | box-shadow: inset 0 0 5px var(--scrollbar-track-color); 115 | } 116 | #scroll-preview::-webkit-scrollbar-thumb { 117 | background: var(--scrollbar-thumb-color); 118 | } 119 | #scroll-preview::-webkit-scrollbar-thumb:hover { 120 | background: var(--scrollbar-thumb-hover-color); 121 | } 122 | `; 123 | 124 | document.head.appendChild(styleTag); 125 | } 126 | 127 | function updatePreview() { 128 | applyPreviewStyles({ 129 | trackColor: trackColor.value, 130 | thumbColor: thumbColor.value, 131 | hoverColor: hoverColor.value, 132 | width: width.value, 133 | }); 134 | } 135 | 136 | [trackColor, thumbColor, hoverColor, width].forEach((input) => { 137 | input?.addEventListener('input', updatePreview); 138 | }); 139 | 140 | updatePreview(); 141 | 142 | getCodeButton?.addEventListener('click', () => 143 | copyHandler({ 144 | trackColor: trackColor.value, 145 | thumbColor: thumbColor.value, 146 | hoverColor: hoverColor.value, 147 | width: width.value, 148 | }) 149 | ); 150 | getCssOrTailwindButtonElement?.addEventListener('click', getCssOrTailwind); 151 | } 152 | -------------------------------------------------------------------------------- /src/pages/text-shadow.ts: -------------------------------------------------------------------------------- 1 | import { 2 | closeDropdown, 3 | copyCSSCodeToClipboard, 4 | copyTailwindCodeToClipboard, 5 | downloadPNG, 6 | downloadSVG, 7 | showPopup, 8 | slideIn, 9 | triggerEmptyAnimation, 10 | } from '../lib/packages/utils'; 11 | import {deleteQueryParam, setQueryParam} from '../lib/packages/helpers'; 12 | import { 13 | getAllFields, 14 | getCopyCodeButton, 15 | getCssOrTailwindButton, 16 | getCssOrTailwindDropdown, 17 | getInputText, 18 | getOpenSideBarButton, 19 | getOutput, 20 | getPNGButton, 21 | getPngOrSvgButton, 22 | getPngOrSvgDropdown, 23 | getPreviewSlider, 24 | getResetButton, 25 | getResultPage, 26 | getSVGButton, 27 | getShadowBlur, 28 | getShadowColor, 29 | getShadowFields, 30 | getShadowHorizontalOffset, 31 | getShadowVerticalOffset, 32 | getTailwindButton, 33 | } from '../lib/getElements'; 34 | 35 | type Values = { 36 | hOffset: string; 37 | vOffset: string; 38 | blur: string; 39 | color: string; 40 | text: string; 41 | }; 42 | 43 | let isSliderOpen = false; 44 | const attribute = 'text-shadow'; 45 | const showCopyClass = 'show-css-tailwind'; 46 | const showPngOrSvgClass = 'show-png-svg'; 47 | 48 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 49 | const getPngOrSvgDropdownElement = getPngOrSvgDropdown(attribute); 50 | const horizontalOffset = getShadowHorizontalOffset(attribute); 51 | const verticalOffset = getShadowVerticalOffset(attribute); 52 | const blur = getShadowBlur(attribute); 53 | const color = getShadowColor(attribute); 54 | const preview = getPreviewSlider(attribute); 55 | const getInputElement = getInputText(attribute); 56 | 57 | function copyHandler() { 58 | const outputElement = getOutput(attribute); 59 | copyCSSCodeToClipboard(attribute, outputElement); 60 | showPopup( 61 | 'Code Copied', 62 | 'Code has been successfully copied to clipboard', 63 | 'success' 64 | ); 65 | } 66 | 67 | function getCssOrTailwind(e?: MouseEvent): void { 68 | e?.stopPropagation(); 69 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 70 | } 71 | 72 | function getPngOrSvg(e?: MouseEvent) { 73 | e?.stopPropagation(); 74 | getPngOrSvgDropdownElement.classList.toggle(showPngOrSvgClass); 75 | } 76 | 77 | // closes css and tailwind dropdown on outside click 78 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 79 | 80 | // closes png and css dropdown outside click 81 | closeDropdown(getPngOrSvg, getPngOrSvgDropdownElement, showPngOrSvgClass); 82 | 83 | function pngDownloadHandler() { 84 | const outputElement = getOutput(attribute); 85 | downloadPNG(attribute, outputElement); 86 | } 87 | 88 | function svgDownloadHanlder() { 89 | const outputElement = getOutput(attribute); 90 | downloadSVG(attribute, outputElement); 91 | } 92 | 93 | export function textShadowGenerator( 94 | type: 'newResults' | 'oldResults' | null 95 | ): void { 96 | if (type === null) return; 97 | 98 | const getOutputElement = getOutput(attribute); 99 | const resultPage = getResultPage(); 100 | 101 | if (getInputElement.value.length === 0) { 102 | getOpenSideBarButton().style.display = 'none'; 103 | triggerEmptyAnimation(getInputElement); 104 | return; 105 | } 106 | resultPage.style.display = 'flex'; 107 | if (type === 'oldResults') return; 108 | 109 | const values: Values = { 110 | hOffset: horizontalOffset.value, 111 | vOffset: verticalOffset.value, 112 | blur: blur.value, 113 | color: color.value, 114 | text: getInputElement.value, 115 | }; 116 | 117 | getTextShadowResult(values, getOutputElement); 118 | } 119 | 120 | /** 121 | * sets the result to the output element 122 | * 123 | * @param values values entered by users 124 | * @param outputElement output element to display result 125 | */ 126 | function getTextShadowResult(values: Values, outputElement: HTMLElement): void { 127 | const createTextShadowElement = ( 128 | textShadowElement: HTMLElement, 129 | values: Values 130 | ) => { 131 | textShadowElement.innerText = values.text; 132 | textShadowElement.style.textShadow = `${values.hOffset}px ${values.vOffset}px ${values.blur}px ${values.color}`; 133 | }; 134 | createTextShadowElement(outputElement, values); 135 | 136 | const getCodeButtonElement = getCopyCodeButton(attribute); 137 | getCodeButtonElement.addEventListener('click', copyHandler); 138 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 139 | getTailwindCodeButtonElement.addEventListener('click', tailwindHandler); 140 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 141 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 142 | const getPngOrSvgButtonElement = getPngOrSvgButton(attribute); 143 | getPngOrSvgButtonElement.addEventListener('click', getPngOrSvg); 144 | } 145 | 146 | export function addTextShadowListener(): void { 147 | const horizontalOffset = getShadowHorizontalOffset(attribute); 148 | const verticalOffset = getShadowVerticalOffset(attribute); 149 | const blur = getShadowBlur(attribute); 150 | const color = getShadowColor(attribute); 151 | 152 | const getInputElement = getInputText(attribute); 153 | const getPNGButtonElement = getPNGButton(attribute); 154 | const getSVGButtonElement = getSVGButton(attribute); 155 | 156 | const allTextShadowInputs = [horizontalOffset, verticalOffset, blur, color]; 157 | const allTextShadowInputsFields = getShadowFields(attribute, [ 158 | 'h-offset', 159 | 'v-offset', 160 | 'blur', 161 | 'text', 162 | ]); 163 | 164 | const getShadowValue = () => 165 | `${horizontalOffset.value}px ${verticalOffset.value}px ${blur.value}px ${color.value}`; 166 | 167 | preview.style.textShadow = getShadowValue(); 168 | 169 | allTextShadowInputs.forEach((input, idx) => { 170 | // default 171 | if (idx < 3) { 172 | allTextShadowInputsFields[idx].textContent = `${input.value}px`; 173 | } 174 | 175 | getInputElement.addEventListener('input', () => { 176 | preview.innerText = getInputElement.value; 177 | preview.style.textShadow = getShadowValue(); 178 | }); 179 | 180 | input.addEventListener('input', () => { 181 | if (getInputElement.value === '') return; 182 | preview.innerText = getInputElement.value; 183 | slideIn(preview, isSliderOpen); 184 | 185 | isSliderOpen = true; 186 | 187 | if (idx < 3) { 188 | allTextShadowInputsFields[idx].textContent = `${input.value}px`; 189 | } 190 | 191 | setQueryParam('values', getShadowValue().trim().replaceAll(' ', '')); 192 | preview.style.textShadow = getShadowValue(); 193 | }); 194 | 195 | getPNGButtonElement.addEventListener('click', pngDownloadHandler); 196 | 197 | getSVGButtonElement.addEventListener('click', svgDownloadHanlder); 198 | }); 199 | } 200 | 201 | // reset the values of all target fields 202 | 203 | function resetValues() { 204 | const {inputs, textarea} = getAllFields(attribute); 205 | 206 | getResetButton(attribute).addEventListener('click', () => { 207 | inputs.forEach((input) => { 208 | input.value = input.defaultValue; 209 | }); 210 | 211 | textarea.value = textarea.defaultValue; 212 | 213 | document.querySelector( 214 | "[data-content='text-shadow'] #text-shadow-h-offset-field" 215 | )!.innerHTML = '2px'; 216 | document.querySelector( 217 | "[data-content='text-shadow'] #text-shadow-v-offset-field" 218 | )!.innerHTML = '2px'; 219 | document.querySelector( 220 | "[data-content='text-shadow'] #text-shadow-blur-field" 221 | )!.innerHTML = '4px'; 222 | 223 | getResetButton(attribute).classList.remove('reset-show'); 224 | 225 | deleteQueryParam('values'); 226 | }); 227 | } 228 | 229 | // get values from all targets to get notified when values change. 230 | 231 | function getValues() { 232 | const {inputs, textarea} = getAllFields(attribute); 233 | 234 | inputs.forEach((input) => { 235 | input.addEventListener('input', () => { 236 | if (input.value !== '' || input.value !== input.defaultValue) { 237 | getResetButton(attribute).classList.add('reset-show'); 238 | resetValues(); 239 | } 240 | }); 241 | }); 242 | 243 | textarea.addEventListener('input', () => { 244 | if (textarea.value !== '') { 245 | resetValues(); 246 | getResetButton(attribute).classList.add('reset-show'); 247 | } 248 | }); 249 | } 250 | getValues(); 251 | 252 | // Tailwind codecopy handler 253 | function tailwindHandler() { 254 | const outputElement: HTMLElement = getOutput(attribute); 255 | copyTailwindCodeToClipboard(attribute, outputElement); 256 | showPopup( 257 | 'Tailwind Code Copied', 258 | 'Code has been successfully copied to clipboard', 259 | 'success' 260 | ); 261 | } 262 | 263 | export const applyTextShadowValues = (values: string) => { 264 | values = values.replaceAll('px', ' '); 265 | const [horizontalOffsetValue, verticalOffsetValue, blurValue, colorValue] = 266 | values.split(' '); 267 | 268 | const textValue = 'hello'; 269 | 270 | getInputElement.value = textValue; 271 | preview.innerText = textValue; 272 | 273 | document.querySelector( 274 | "[data-content='text-shadow'] #text-shadow-h-offset-field" 275 | )!.innerHTML = horizontalOffsetValue + 'px'; 276 | document.querySelector( 277 | "[data-content='text-shadow'] #text-shadow-v-offset-field" 278 | )!.innerHTML = verticalOffsetValue + 'px'; 279 | document.querySelector( 280 | "[data-content='text-shadow'] #text-shadow-blur-field" 281 | )!.innerHTML = blurValue + 'px'; 282 | 283 | horizontalOffset.value = horizontalOffsetValue; 284 | verticalOffset.value = verticalOffsetValue; 285 | blur.value = blurValue; 286 | color.value = colorValue; 287 | 288 | slideIn(preview, isSliderOpen); 289 | 290 | isSliderOpen = true; 291 | 292 | console.log({values}); 293 | 294 | preview.style.textShadow = values.replaceAll(' ', 'px '); 295 | }; 296 | -------------------------------------------------------------------------------- /src/pages/transform.ts: -------------------------------------------------------------------------------- 1 | import {closeDropdown, showPopup, slideIn} from '../lib/packages/utils'; 2 | import {deleteQueryParam, setQueryParam} from '../lib/packages/helpers'; 3 | import { 4 | getAllFields, 5 | getCopyCodeButton, 6 | getCssOrTailwindButton, 7 | getCssOrTailwindDropdown, 8 | getOutput, 9 | getPreviewSlider, 10 | getRadioButtonSet, 11 | getRange, 12 | getResetButton, 13 | getResultPage, 14 | getTailwindButton, 15 | } from '../lib/getElements'; 16 | 17 | import copy from 'copy-to-clipboard'; 18 | 19 | type Values = { 20 | type: string; 21 | degree: string; 22 | }; 23 | 24 | const attribute = 'transform'; 25 | 26 | const transformExports = { 27 | scale: 0, 28 | skew: '', 29 | rotate: '', 30 | translateX: '', 31 | translateY: '', 32 | }; 33 | 34 | let css = ''; 35 | let tailwindCss = ''; 36 | let isSliderOpen = false; 37 | 38 | const preview = getPreviewSlider(attribute); 39 | const getDegreeElement = getRange(attribute); 40 | const getRadioButtonSetElement = getRadioButtonSet(attribute); 41 | const getCssOrTailwindDropdownElement = getCssOrTailwindDropdown(attribute); 42 | const showCopyClass = 'show-css-tailwind'; 43 | 44 | function getCssOrTailwind(e?: MouseEvent): void { 45 | e?.stopPropagation(); 46 | getCssOrTailwindDropdownElement.classList.toggle(showCopyClass); 47 | } 48 | 49 | // closes css and tailwind dropdown on outside click 50 | closeDropdown(getCssOrTailwind, getCssOrTailwindDropdownElement, showCopyClass); 51 | 52 | export function addTransformListener(): void { 53 | // Listen for radio button changes 54 | getRadioButtonSetElement.forEach((option) => { 55 | option.addEventListener('change', () => { 56 | // resets input element value when radio button changes 57 | getDegreeElement.valueAsNumber = 1; 58 | 59 | // Update preview with default value for selected option 60 | updatePreviewAndRange(option.value, getDegreeElement.valueAsNumber); 61 | }); 62 | }); 63 | 64 | // Listen for range input changes 65 | getDegreeElement.addEventListener('input', () => { 66 | let i = 0; 67 | for (i = 0; i < getRadioButtonSetElement.length; i++) 68 | if (getRadioButtonSetElement[i].checked) break; 69 | 70 | const type = getRadioButtonSetElement[i].value; 71 | if (type === '') return; 72 | 73 | slideIn(preview, isSliderOpen); 74 | isSliderOpen = true; 75 | // Update preview with current value for selected option 76 | const selectedOption = document.querySelector( 77 | 'input[name="transform-radio"]:checked' 78 | ) as HTMLInputElement; 79 | setQueryParam( 80 | 'values', 81 | JSON.stringify({ 82 | type: selectedOption.value, 83 | degree: getDegreeElement.value, 84 | }) 85 | ); 86 | updatePreviewAndRange(selectedOption.value, getDegreeElement.valueAsNumber); 87 | }); 88 | } 89 | 90 | function updatePreviewAndRange(type: string, value: number) { 91 | const unitDisplayElement = document.querySelector( 92 | '.unit-display.transform' 93 | ) as HTMLElement; 94 | 95 | const titleDisplayElement = document.querySelector( 96 | '.title-display.transform' 97 | ) as HTMLElement; 98 | 99 | switch (type) { 100 | case 'scale': 101 | preview.style.transform = `scale(${value})`; 102 | transformExports.scale = value; 103 | getDegreeElement.min = '.1'; 104 | getDegreeElement.max = '2'; 105 | getDegreeElement.step = '.1'; 106 | unitDisplayElement.innerText = `${value}`; 107 | titleDisplayElement.innerText = 'Size'; 108 | break; 109 | case 'skew': 110 | preview.style.transform = `skew(${value}deg)`; 111 | transformExports.skew = value.toString(); 112 | getDegreeElement.min = '-180'; 113 | getDegreeElement.max = '180'; 114 | getDegreeElement.step = '1'; 115 | unitDisplayElement.innerText = `${value}deg`; 116 | titleDisplayElement.innerText = 'Degree'; 117 | break; 118 | case 'translateX': 119 | preview.style.transform = `translateX(${value}px)`; 120 | transformExports.translateX = value.toString(); 121 | getDegreeElement.min = '-100'; 122 | getDegreeElement.max = '100'; 123 | getDegreeElement.step = '1'; 124 | unitDisplayElement.innerText = `${value}px`; 125 | titleDisplayElement.innerText = 'Position'; 126 | break; 127 | case 'translateY': 128 | preview.style.transform = `translateY(${value}px)`; 129 | transformExports.translateY = value.toString(); 130 | getDegreeElement.min = '-100'; 131 | getDegreeElement.max = '100'; 132 | getDegreeElement.step = '1'; 133 | unitDisplayElement.innerText = `${value}px`; 134 | titleDisplayElement.innerText = 'Position'; 135 | break; 136 | case 'rotate': 137 | preview.style.transform = `rotate(${value}deg)`; 138 | transformExports.rotate = value.toString(); 139 | getDegreeElement.min = '0'; 140 | getDegreeElement.max = '360'; 141 | getDegreeElement.step = '1'; 142 | unitDisplayElement.innerText = `${value}deg`; 143 | titleDisplayElement.innerText = 'Degrees'; 144 | break; 145 | default: 146 | break; 147 | } 148 | } 149 | 150 | export function transformGenerator( 151 | type: 'newResults' | 'oldResults' | null 152 | ): void { 153 | if (type === null) return; 154 | 155 | const getOutputElement = getOutput(attribute); 156 | const resultPage = getResultPage(); 157 | 158 | resultPage.style.display = 'flex'; 159 | if (getOutputElement === null || type === 'oldResults') return; 160 | 161 | getTransformResult(getOutputElement); 162 | } 163 | 164 | function getTransformResult(outputElement: HTMLElement): void { 165 | const createTransformElement = (): void => { 166 | outputElement.style.width = '200px'; 167 | outputElement.style.height = '200px'; 168 | }; 169 | createTransformElement(); 170 | 171 | let i = 0; 172 | 173 | for (i = 0; i < getRadioButtonSetElement.length; i++) 174 | if (getRadioButtonSetElement[i].checked) break; 175 | 176 | const values: Values = { 177 | type: getRadioButtonSetElement[i].value, 178 | degree: getDegreeElement.value, 179 | }; 180 | 181 | manageTransform(values, outputElement); 182 | manageTailwindTransform(values); 183 | 184 | const getCodeButtonElement = getCopyCodeButton(attribute); 185 | getCodeButtonElement.style.zIndex = '100'; 186 | getCodeButtonElement.addEventListener('click', () => { 187 | copy(css); 188 | showPopup( 189 | 'Code Copied', 190 | 'Code has been successfully copied to clipboard', 191 | 'success' 192 | ); 193 | }); 194 | const getTailwindCodeButtonElement = getTailwindButton(attribute); 195 | getTailwindCodeButtonElement.addEventListener('click', () => { 196 | copy(tailwindCss); 197 | showPopup( 198 | 'Tailwind Code Copied', 199 | 'Code has been successfully copied to clipboard', 200 | 'success' 201 | ); 202 | }); 203 | const getCssOrTailwindButtonElement = getCssOrTailwindButton(attribute); 204 | getCssOrTailwindButtonElement.addEventListener('click', getCssOrTailwind); 205 | } 206 | 207 | function manageTransform(values: Values, getOutputElement: HTMLElement) { 208 | switch (values.type) { 209 | case 'scale': 210 | css = ` 211 | transform: scale(${values.degree}); 212 | -webkit-transform: scale(${values.degree}); 213 | -moz-transform: scale(${values.degree});`; 214 | getOutputElement.style.transform = `scale(${values.degree})`; 215 | break; 216 | case 'skew': 217 | css = ` 218 | transform: skew(${values.degree}deg); 219 | -webkit-transform: skew(${values.degree}deg); 220 | -moz-transform: skew(${values.degree}deg);`; 221 | getOutputElement.style.transform = `skew(${values.degree}deg)`; 222 | break; 223 | case 'translateX': 224 | css = ` 225 | transform: translateX(${values.degree}px); 226 | -webkit-transform: translateX(${values.degree}px); 227 | -moz-transform: translateX(${values.degree}px);`; 228 | getOutputElement.style.transform = `translateX(${values.degree}px)`; 229 | break; 230 | case 'translateY': 231 | css = ` 232 | transform: translateY(${values.degree}px); 233 | -webkit-transform: translateY(${values.degree}px); 234 | -moz-transform: translateY(${values.degree}px);`; 235 | getOutputElement.style.transform = `translateY(${values.degree}px)`; 236 | break; 237 | case 'rotate': 238 | css = ` 239 | transform: rotate(${values.degree}deg); 240 | -webkit-transform: rotate(${values.degree}deg); 241 | -moz-transform: rotate(${values.degree}deg);`; 242 | getOutputElement.style.transform = `rotate(${values.degree}deg)`; 243 | break; 244 | default: 245 | break; 246 | } 247 | } 248 | 249 | function resetValues() { 250 | const {inputs} = getAllFields(attribute); 251 | 252 | getResetButton(attribute).addEventListener('click', () => { 253 | inputs.forEach((input) => { 254 | input.value = input.defaultValue; 255 | input.checked = input.defaultChecked; 256 | }); 257 | 258 | getResetButton(attribute).classList.remove('reset-show'); 259 | }); 260 | 261 | deleteQueryParam('values'); 262 | } 263 | 264 | // get values from all targets to get notified when values change. 265 | 266 | function getValues() { 267 | const {inputs} = getAllFields(attribute); 268 | 269 | inputs.forEach((input) => { 270 | input.addEventListener('input', () => { 271 | if ( 272 | input.checked !== input.defaultChecked || 273 | inputs[5].value !== inputs[5].defaultValue 274 | ) { 275 | getResetButton(attribute).classList.add('reset-show'); 276 | resetValues(); 277 | } 278 | }); 279 | }); 280 | } 281 | getValues(); 282 | 283 | // Function to get tailwind styles for transform 284 | function manageTailwindTransform(values: Values) { 285 | switch (values.type) { 286 | case 'scale': 287 | tailwindCss = `scale`; 288 | break; 289 | case 'skew': 290 | tailwindCss = `skew`; 291 | break; 292 | case 'translateX': 293 | tailwindCss = `translateX`; 294 | break; 295 | case 'translateY': 296 | tailwindCss = `Translatey`; 297 | break; 298 | case 'rotate': 299 | tailwindCss = `Rotate`; 300 | break; 301 | default: 302 | break; 303 | } 304 | } 305 | 306 | export const applyTransformValue = (values: string) => { 307 | const {degree, type}: Values = JSON.parse(values); 308 | 309 | getRadioButtonSetElement.forEach((item) => { 310 | if (item.value === type) { 311 | item.checked = true; 312 | } 313 | }); 314 | 315 | slideIn(preview, isSliderOpen); 316 | isSliderOpen = true; 317 | updatePreviewAndRange(type, Number(degree)); 318 | getDegreeElement.value = degree; 319 | }; 320 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Colors */ 3 | --primary-color: #081121; 4 | --text-color: #edf2f4; 5 | --input-color: #000000; 6 | --secondary-color: #0e213c; 7 | --tertiary-color: #6c9dc5; 8 | --gradient-border-color-1: red; 9 | --gradient-border-color-2: blue; 10 | 11 | /* Widths */ 12 | --generator-width: clamp(300px, 80%, 50vw); 13 | --content-container: 100%; 14 | --input-width: 300px; 15 | --output-width: 300px; 16 | --pseudo-footer-height: 70px; 17 | 18 | /* GRADIENT BORDER SECTION */ 19 | --gradient-border-radius: 0; 20 | --gradient-border-degree: 45deg; 21 | 22 | /* Spacing */ 23 | --spacing: 5px; 24 | 25 | /* Preview Track Styling */ 26 | --preview-track-height: 0.5rem; 27 | --preview-track-width: 100%; 28 | --preview-track-radius: 10px; 29 | --preview-track-color: var(--secondary-color); 30 | 31 | /* Preview Track Styling */ 32 | --preview-thumb-height: 1rem; 33 | --preview-thumb-width: 1rem; 34 | --preview-thumb-radius: 10px; 35 | --preview-thumb-color: var(--tertiary-color); 36 | } 37 | 38 | *, 39 | *::after, 40 | *::before { 41 | user-select: none; 42 | 43 | margin: 0; 44 | padding: 0; 45 | box-sizing: border-box; 46 | } 47 | 48 | ::placeholder { 49 | color: #ffffff; 50 | } 51 | 52 | body { 53 | font-family: 'Exo 2', 'Courier New', Courier, monospace; 54 | color: var(--text-color); 55 | overflow: hidden; 56 | padding-bottom: var(--pseudo-footer-height); 57 | display: flex; 58 | } 59 | 60 | nav { 61 | display: flex; 62 | flex-direction: column; 63 | position: relative; 64 | font-size: 1.3rem; 65 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); 66 | padding: 0 1.5rem; 67 | min-width: 20vw; 68 | height: 100vh; 69 | background-color: var(--primary-color); 70 | z-index: 99; 71 | } 72 | 73 | .menu { 74 | display: none; 75 | } 76 | 77 | #menu-icon { 78 | cursor: pointer; 79 | } 80 | 81 | nav h1 { 82 | margin: 4rem 0 1rem 0; 83 | font-size: 1.5rem; 84 | background: linear-gradient(90deg, #ffffff, var(--secondary-color), #fff); 85 | -webkit-background-clip: text; 86 | -webkit-text-fill-color: transparent; 87 | background-clip: text; 88 | background-size: 70% 100%; 89 | cursor: pointer; 90 | animation: moveColor 7s linear infinite; 91 | animation-fill-mode: both; 92 | animation-direction: alternate; 93 | } 94 | 95 | nav ul { 96 | list-style: none; 97 | } 98 | 99 | .dropdown { 100 | display: flex; 101 | justify-content: center; 102 | } 103 | 104 | .dropdown div { 105 | display: flex; 106 | align-items: center; 107 | cursor: pointer; 108 | justify-content: space-between; 109 | } 110 | 111 | .dropdown > div iconify-icon:first-of-type { 112 | margin-right: 5px; 113 | } 114 | 115 | .dropdown > ul { 116 | visibility: hidden; 117 | position: relative; 118 | max-height: 0px; 119 | opacity: 0; 120 | transform: scaley(0); 121 | transform-origin: 50% 0; 122 | margin-bottom: 10px; 123 | } 124 | 125 | .dropdown > ul { 126 | transition-property: transform, max-height, opacity; 127 | transition-duration: 350ms; 128 | /* transition: transform 350ms, max-height 350ms, opacity 350ms; */ 129 | transition-timing-function: ease-in-out; 130 | } 131 | 132 | /* .dropdown:hover > ul, */ 133 | #showList { 134 | visibility: visible; 135 | max-height: 50px; 136 | opacity: 1; 137 | padding: 0.625rem; 138 | transform: scale(1) translateX(10px); 139 | } 140 | 141 | #category { 142 | margin-top: 20px; 143 | display: flex; 144 | flex-direction: column; 145 | gap: 10px; 146 | } 147 | 148 | #category > li { 149 | font-size: 1rem; 150 | padding-left: 0.2rem; 151 | display: flex; 152 | padding-top: 0.625rem; 153 | flex-direction: column; 154 | } 155 | 156 | #category ul { 157 | border-radius: 3px; 158 | display: flex; 159 | flex-direction: column; 160 | font-size: 0.85rem; 161 | } 162 | 163 | #category ul > li { 164 | display: flex; 165 | align-items: center; 166 | border-radius: 3px; 167 | padding: 0.35rem; 168 | } 169 | 170 | #category ul > li span { 171 | margin-left: 0.2rem; 172 | } 173 | 174 | #category ul > li span.tooltip { 175 | position: absolute; 176 | right: -140px; 177 | padding: 5px; 178 | width: 145px; 179 | color: var(--input-color); 180 | background-color: var(--text-color); 181 | border-radius: 5px; 182 | display: none; 183 | } 184 | 185 | #category ul > li.tooltip::before { 186 | content: ''; 187 | position: absolute; 188 | top: 50%; 189 | left: -8%; 190 | margin-top: -5px; 191 | border-width: 6px; 192 | border-style: solid; 193 | border-color: transparent var(--text-color) transparent transparent; 194 | } 195 | 196 | #category ul > li:hover .tooltip { 197 | display: block; 198 | } 199 | 200 | #category ul > li:hover { 201 | background: linear-gradient( 202 | 90deg, 203 | var(--primary-color), 204 | var(--secondary-color) 205 | ) !important; 206 | cursor: pointer; 207 | } 208 | 209 | main { 210 | width: var(--content-container); 211 | min-height: 100vh; 212 | display: flex; 213 | justify-content: center; 214 | align-items: center; 215 | background-color: var(--secondary-color); 216 | position: relative; 217 | } 218 | 219 | main > section { 220 | min-height: var(--content-container); 221 | min-width: var(--content-container); 222 | display: flex; 223 | flex-direction: column; 224 | justify-content: center; 225 | align-items: center; 226 | padding: 0 1rem 0 1rem; 227 | } 228 | 229 | main > section:first-of-type h1 { 230 | font-size: clamp(1rem, 2.5vw, 3rem); 231 | text-align: center; 232 | font-weight: 600; 233 | } 234 | 235 | main > section:first-of-type h2 { 236 | margin-top: 10px; 237 | font-size: clamp(0.7rem, 1vw, 2.7rem); 238 | text-align: center; 239 | font-weight: 500; 240 | } 241 | 242 | main > section:first-of-type span { 243 | color: var(--tertiary-color); 244 | font-style: italic; 245 | text-transform: uppercase; 246 | } 247 | 248 | main > section:last-of-type { 249 | display: none; 250 | position: relative; 251 | } 252 | 253 | .open { 254 | display: flex; 255 | align-items: center; 256 | position: absolute; 257 | top: 10px; 258 | right: 50px; 259 | } 260 | 261 | .open > p { 262 | opacity: 0; 263 | } 264 | 265 | .generators { 266 | min-height: var(--content-container); 267 | min-width: var(--content-container); 268 | display: flex; 269 | flex-direction: column; 270 | align-items: center; 271 | justify-content: center; 272 | position: relative; 273 | } 274 | 275 | .generator-tool-title { 276 | position: fixed; 277 | top: 4%; 278 | } 279 | 280 | .generator-tool-title h1 { 281 | font-size: 2.25rem; 282 | font-weight: 600; 283 | } 284 | 285 | /* inputs */ 286 | .generators > div { 287 | display: none; 288 | width: var(--generator-width); 289 | flex-direction: column; 290 | justify-content: center; 291 | align-items: center; 292 | } 293 | 294 | .preview-slider { 295 | height: 80px; 296 | width: 200px; 297 | 298 | position: absolute; 299 | left: -300px; 300 | border: 1px solid #fff; 301 | background: transparent; 302 | } 303 | 304 | [data-content='box-shadow'] .preview-slider { 305 | top: -79%; 306 | } 307 | 308 | [data-content='text-shadow'] .preview-slider { 309 | top: -30%; 310 | word-wrap: break-word; 311 | font-size: 0.8rem; 312 | display: flex; 313 | justify-content: center; 314 | align-items: center; 315 | } 316 | 317 | [data-content='animation'] .preview-slider { 318 | top: -50%; 319 | } 320 | 321 | [data-content='transform'] .preview-slider { 322 | top: -50%; 323 | } 324 | 325 | [data-content='pic-text'] { 326 | display: flex; 327 | } 328 | 329 | .filepond--drop-label { 330 | border-radius: 8px; 331 | border: 2px dashed var(--tertiary-color); 332 | } 333 | 334 | [data-content='pic-text'] > label:first-of-type { 335 | width: var(--generator-width); 336 | } 337 | 338 | [data-content='input-range'] { 339 | gap: 5px; 340 | } 341 | 342 | [data-content='input-range'] > label:first-of-type { 343 | background-color: var(--primary-color); 344 | width: var(--generator-width); 345 | display: flex; 346 | flex-direction: column; 347 | gap: 0.5rem; 348 | padding: 1rem; 349 | justify-content: center; 350 | height: fit-content; 351 | border-radius: 5px; 352 | margin-bottom: 20px; 353 | } 354 | 355 | [data-content='input-range'] > label:first-of-type p { 356 | font-size: 0.7em; 357 | } 358 | 359 | .radius, 360 | .size { 361 | background-color: var(--primary-color); 362 | padding: 0.8rem; 363 | border-radius: 5px; 364 | width: var(--generator-width); 365 | } 366 | 367 | .size > div { 368 | display: flex; 369 | justify-content: space-between; 370 | min-width: 100%; 371 | } 372 | 373 | .size label { 374 | display: flex; 375 | flex-direction: column; 376 | width: 45%; 377 | } 378 | 379 | .size label > div { 380 | display: flex; 381 | justify-content: space-between; 382 | } 383 | 384 | .size > div div { 385 | margin: 0.5rem 0; 386 | font-size: 0.8rem; 387 | } 388 | 389 | textarea { 390 | border-radius: 5px; 391 | width: var(--generator-width); 392 | height: 50px; 393 | border: none; 394 | background-color: var(--primary-color); 395 | color: var(--text-color); 396 | resize: none; 397 | padding: 1rem; 398 | margin-bottom: var(--spacing); 399 | outline: none; 400 | } 401 | 402 | input::placeholder { 403 | color: #484848; 404 | } 405 | 406 | .colors, 407 | [data-content='scroll'] > .input { 408 | display: flex; 409 | flex-wrap: wrap; 410 | align-items: center; 411 | margin: var(--spacing) 0 var(--spacing) 0; 412 | width: var(--generator-width); 413 | } 414 | 415 | .add-remove-color { 416 | display: flex; 417 | justify-content: space-between; 418 | flex-wrap: wrap; 419 | } 420 | 421 | .add-remove-color > div:nth-of-type(even) { 422 | margin-left: 5px; 423 | } 424 | 425 | .newColor { 426 | display: flex; 427 | align-items: center; 428 | background-color: var(--primary-color); 429 | padding: 5px; 430 | border-radius: 2px; 431 | cursor: pointer; 432 | } 433 | 434 | .preview { 435 | margin: var(--spacing) 0 var(--spacing) 0; 436 | width: var(--generator-width); 437 | height: 50px; 438 | background-color: var(--primary-color); 439 | border-radius: 5px; 440 | } 441 | 442 | .colors > .color:nth-of-type(even) { 443 | margin-left: 10px; 444 | } 445 | 446 | .color, 447 | [data-content='scroll'] > .input label[for='width'] { 448 | display: flex; 449 | flex-direction: column; 450 | gap: 0.5rem; 451 | justify-content: center; 452 | align-items: center; 453 | width: 48%; 454 | margin-bottom: 5px; 455 | } 456 | 457 | .full-page { 458 | width: 100% !important; 459 | } 460 | 461 | .clr-field { 462 | width: 100%; 463 | display: flex; 464 | align-items: center; 465 | justify-content: center; 466 | } 467 | 468 | .clr-field > button { 469 | width: 40px !important; 470 | } 471 | 472 | [data-coloris] { 473 | width: 100%; 474 | height: 30px; 475 | background: none; 476 | border: 1px solid #fff; 477 | padding: 10px; 478 | outline: none; 479 | color: white; 480 | border-radius: 5px; 481 | } 482 | 483 | [data-coloris]::placeholder { 484 | color: #fff; 485 | } 486 | 487 | .animation-type, 488 | .transform-type { 489 | background-color: var(--primary-color); 490 | width: var(--generator-width); 491 | padding: 1rem; 492 | border-radius: 5px; 493 | } 494 | 495 | .animation-type > p, 496 | .transform-type > p { 497 | margin-bottom: 0.5rem; 498 | } 499 | 500 | .radio-group { 501 | display: flex; 502 | justify-content: space-between; 503 | flex-wrap: wrap; 504 | } 505 | 506 | #animation-radio-label, 507 | #transform-radio-label { 508 | display: flex; 509 | width: 6rem; 510 | padding: 0; 511 | margin: 0; 512 | background: none; 513 | font-weight: 600; 514 | color: var(--text-color); 515 | font-size: 0.9em; 516 | } 517 | 518 | #animation-radio-label > span { 519 | padding: 0 8px 0 4px; 520 | } 521 | 522 | #radius-input-label { 523 | display: flex; 524 | justify-content: center; 525 | ::-ms-input-placeholder { 526 | color: var(--text-color); 527 | } 528 | caret-color: var(--text-color); 529 | } 530 | 531 | .toggle-radius { 532 | display: none; 533 | min-width: 0; 534 | } 535 | .track-input, 536 | .thumb-input { 537 | width: 80%; 538 | padding: 5px; 539 | } 540 | .input-range-container { 541 | display: flex; 542 | align-items: center; 543 | justify-content: space-between; 544 | padding: 5px; 545 | } 546 | .checkbox-title { 547 | display: flex; 548 | align-items: center; 549 | gap: 5px; 550 | } 551 | .circular-checkbox { 552 | display: flex; 553 | flex-shrink: 0; 554 | width: 20px; 555 | height: 20px; 556 | border-radius: 50%; 557 | border: 2px solid #fff; 558 | appearance: none; 559 | outline: none; 560 | cursor: pointer; 561 | position: relative; 562 | } 563 | 564 | /* Style for checked circular checkbox */ 565 | .circular-checkbox:checked { 566 | background-color: var(--tertiary-color); 567 | } 568 | .circular-checkbox:checked::before { 569 | content: '\2713'; /* Unicode checkmark character */ 570 | display: block; 571 | width: 100%; 572 | height: 100%; 573 | text-align: center; 574 | line-height: 1.4; /* Adjust line height for vertical centering */ 575 | color: #fff; /* Color for the tick mark */ 576 | position: absolute; 577 | top: 0; 578 | left: 0; 579 | } 580 | 581 | #animation-duration { 582 | background: #0e213c; 583 | border: 1px solid black; 584 | border-radius: 5px; 585 | -webkit-text-fill-color: white; 586 | padding: 4px; 587 | font-family: inherit; 588 | } 589 | 590 | #gradient-border-input { 591 | background: white; 592 | border: 1px solid black; 593 | border-radius: 5px; 594 | -webkit-text-fill-color: black; 595 | padding: 4px; 596 | font-family: inherit; 597 | } 598 | 599 | input[type='number']::-webkit-inner-spin-button, 600 | input[type='number']::-webkit-outer-spin-button { 601 | -webkit-appearance: none; 602 | -moz-appearance: none; 603 | appearance: none; 604 | margin: 0; 605 | } 606 | 607 | #degree { 608 | display: flex; 609 | flex-direction: column; 610 | background-color: var(--primary-color); 611 | padding: 0.8rem; 612 | border-radius: 5px; 613 | width: var(--generator-width); 614 | margin-top: var(--spacing); 615 | } 616 | 617 | #degree .label-title { 618 | margin-bottom: 0.6rem; 619 | font-size: 0.9em; 620 | display: flex; 621 | justify-content: space-between; 622 | align-items: center; 623 | } 624 | 625 | .unit-display { 626 | opacity: 0.7; 627 | } 628 | 629 | .duration-label { 630 | background-color: var(--primary-color); 631 | width: var(--generator-width); 632 | padding: 1rem; 633 | border-radius: 5px; 634 | margin-top: 1rem; 635 | margin-bottom: 1rem; 636 | display: flex; 637 | justify-content: space-between; 638 | align-items: center; 639 | flex-wrap: wrap; 640 | } 641 | 642 | .styled-input, 643 | .input-range-inputs[type='number'] { 644 | background-color: var(--secondary-color); 645 | border: 1px solid #010204; 646 | border-radius: 5px; 647 | color: var(--text-color); 648 | caret-color: var(--text-color); 649 | padding: 4px; 650 | } 651 | 652 | .input-range-inputs[type='number']::placeholder { 653 | color: var(--text-color); 654 | } 655 | 656 | .styled-input:focus, 657 | .styled-input:focus-visible, 658 | .input-range-inputs[type='number']:focus, 659 | .input-range-inputs[type='number']:focus-visible { 660 | outline: none; 661 | } 662 | 663 | .styled-input::-webkit-inner-spin-button, 664 | .styled-input::-webkit-outer-spin-button, 665 | .input-range-inputs[type='number']::-webkit-inner-spin-button, 666 | .input-range-inputs[type='number']::-webkit-outer-spin-button { 667 | -webkit-appearance: none; 668 | appearance: none; 669 | margin: 0; 670 | } 671 | 672 | .duration-label > span { 673 | font-size: 0.9em; 674 | } 675 | 676 | .btn-container { 677 | width: var(--generator-width); 678 | display: flex; 679 | justify-content: center; 680 | align-items: center; 681 | gap: 20px; 682 | } 683 | 684 | .btn-container .reset { 685 | height: 40px; 686 | width: 150px; 687 | border-radius: 5px; 688 | align-items: center; 689 | justify-content: center; 690 | background-color: var(--primary-color); 691 | color: var(--text-color); 692 | cursor: pointer; 693 | margin-top: calc(var(--spacing) + 10px); 694 | border: none; 695 | display: none; 696 | } 697 | 698 | .btn-container .reset-show { 699 | animation: 1s reset-show; 700 | display: flex; 701 | } 702 | 703 | @keyframes reset-show { 704 | from { 705 | transform: translateY(-30px); 706 | } 707 | } 708 | 709 | .btn { 710 | height: 40px; 711 | width: 150px; 712 | border-radius: 5px; 713 | display: flex; 714 | align-items: center; 715 | justify-content: center; 716 | background-color: var(--primary-color); 717 | color: var(--text-color); 718 | cursor: pointer; 719 | margin-top: calc(var(--spacing) + 10px); 720 | border: none; 721 | font-size: 14px; 722 | gap: 0.7rem; 723 | } 724 | 725 | .btn:hover { 726 | background-color: #0c49d2; 727 | } 728 | 729 | .degrees { 730 | width: var(--generator-width); 731 | display: flex; 732 | flex-direction: column; 733 | align-items: center; 734 | margin-top: var(--spacing); 735 | } 736 | 737 | .degrees-row { 738 | display: flex; 739 | flex-direction: row; 740 | justify-content: center; 741 | align-items: center; 742 | background-color: var(--primary-color); 743 | width: var(--content-container); 744 | border-radius: 5px; 745 | } 746 | 747 | .degrees-row label { 748 | width: 40% !important; 749 | } 750 | 751 | .degrees-row label > span:nth-of-type(1) { 752 | display: flex; 753 | justify-content: space-between; 754 | align-items: center; 755 | margin-bottom: 0.3rem; 756 | } 757 | 758 | .degrees-row label > span:nth-of-type(1) > span:nth-last-of-type(1) { 759 | opacity: 0.8; 760 | font-size: 12px; 761 | } 762 | 763 | .box-shadow-preview { 764 | --box-size: 100px; 765 | position: absolute; 766 | top: 1rem; 767 | left: 3.2rem; 768 | width: var(--box-size); 769 | height: var(--box-size); 770 | border: 1px solid rgba(0, 0, 0, 0.2); 771 | } 772 | 773 | .divider { 774 | width: 60%; 775 | border: none; 776 | height: 0.03rem; 777 | background-color: var(--text-color); 778 | } 779 | 780 | /* Results */ 781 | .side-results { 782 | display: none; 783 | background-color: var(--secondary-color); 784 | min-height: var(--content-container); 785 | min-width: var(--content-container); 786 | position: absolute; 787 | left: 100%; 788 | flex-direction: column; 789 | } 790 | 791 | .side-results .header { 792 | padding: 3rem; 793 | } 794 | 795 | .side-results .header, 796 | .close-sideBar, 797 | .open-sidebar { 798 | height: 10vh; 799 | display: flex; 800 | align-items: center; 801 | } 802 | 803 | .side-results .header > h2 { 804 | margin-left: 3rem; 805 | } 806 | 807 | .close-sideBar span.tooltip { 808 | position: relative; 809 | bottom: -20px; 810 | right: 40px; 811 | padding: 5px; 812 | width: 50px; 813 | margin-top: 35px; 814 | margin-right: -50px; 815 | font-size: 15px; 816 | text-align: center; 817 | color: var(--input-color); 818 | background-color: var(--text-color); 819 | border-radius: 5px; 820 | display: none; 821 | } 822 | 823 | .open-sidebar > span.tooltip { 824 | position: relative; 825 | bottom: -35px; 826 | right: 40px; 827 | padding: 5px; 828 | width: 50px; 829 | margin-top: 10px; 830 | margin-right: -50px; 831 | font-size: 15px; 832 | text-align: center; 833 | color: var(--input-color); 834 | background-color: var(--text-color); 835 | border-radius: 5px; 836 | display: none; 837 | } 838 | 839 | .close-sideBar .tooltip::before { 840 | content: ''; 841 | position: absolute; 842 | top: -15%; 843 | left: 8%; 844 | margin-top: -5px; 845 | border-width: 12px; 846 | border-style: solid; 847 | border-color: transparent var(--text-color) transparent transparent; 848 | } 849 | 850 | .open-sidebar > span.tooltip::before { 851 | content: ''; 852 | position: absolute; 853 | top: -15%; 854 | left: 5%; 855 | margin-top: -5px; 856 | border-width: 12px; 857 | border-style: solid; 858 | border-color: transparent var(--text-color) transparent transparent; 859 | } 860 | 861 | .open-sidebar > span.tooltip::before { 862 | top: -26%; 863 | } 864 | 865 | .close-sideBar:hover .tooltip, 866 | .open-sidebar:hover > span.tooltip { 867 | display: block; 868 | } 869 | 870 | .go-back-icon span.tooltip { 871 | position: relative; 872 | bottom: -20px; 873 | right: 40px; 874 | padding: 5px; 875 | width: 50px; 876 | margin-top: 35px; 877 | margin-right: -50px; 878 | font-size: 15px; 879 | text-align: center; 880 | color: var(--input-color); 881 | background-color: var(--text-color); 882 | border-radius: 5px; 883 | display: none; 884 | } 885 | 886 | .go-back-icon .tooltip::before { 887 | content: ''; 888 | position: absolute; 889 | top: -15%; 890 | left: 8%; 891 | margin-top: -5px; 892 | border-width: 12px; 893 | border-style: solid; 894 | border-color: transparent var(--text-color) transparent transparent; 895 | } 896 | 897 | .go-back-icon:hover .tooltip { 898 | display: block; 899 | } 900 | 901 | .output { 902 | width: var(--generator-width); 903 | height: 300px; 904 | word-wrap: break-word; 905 | margin-bottom: 1rem; 906 | background-color: var(--primary-color); 907 | } 908 | 909 | .output:not([data-result='pic-text'] > .output) { 910 | display: grid; 911 | place-items: center; 912 | font-size: clamp(20px, 1vw + 1rem, 3vw); 913 | text-align: center; 914 | } 915 | 916 | .download-output { 917 | flex-direction: column; 918 | justify-content: center; 919 | align-items: center; 920 | margin-top: auto; 921 | margin-bottom: auto; 922 | } 923 | 924 | .gradient-border { 925 | visibility: hidden; 926 | position: relative; 927 | width: var(--output-width); 928 | height: 300px; 929 | margin-bottom: 1rem; 930 | } 931 | 932 | [data-content='border-radius'] { 933 | display: flex; 934 | flex-direction: column; 935 | width: 600px; 936 | position: relative; 937 | } 938 | 939 | .border-radius-preview-box { 940 | width: 250px; 941 | height: 250px; 942 | outline: 1px dashed var(--text-color); 943 | display: flex; 944 | align-items: center; 945 | justify-content: center; 946 | margin-bottom: 1rem; 947 | position: relative; 948 | } 949 | 950 | .border-radius-preview-box + .btn-container { 951 | margin-top: 1rem; 952 | } 953 | 954 | .border-radius-preview-box .preview { 955 | width: inherit; 956 | height: inherit; 957 | background: var(--primary-color); 958 | } 959 | 960 | .border-radius-inputs { 961 | width: 100%; 962 | } 963 | 964 | .radius-checkbox-container { 965 | display: flex; 966 | align-items: center; 967 | gap: 10px; 968 | } 969 | 970 | .radius-input-container { 971 | display: flex; 972 | align-items: center; 973 | justify-content: space-between; 974 | width: 100%; 975 | } 976 | 977 | #border-radius-code { 978 | /* padding: 1rem; */ 979 | background: var(--primary-color); 980 | margin-top: 2rem; 981 | } 982 | 983 | #border-radius-top { 984 | position: absolute; 985 | top: -2rem; 986 | left: 0; 987 | } 988 | 989 | #border-radius-left { 990 | position: absolute; 991 | transform: rotate(90deg); 992 | top: 7rem; 993 | left: -9rem; 994 | } 995 | 996 | #border-radius-right { 997 | position: absolute; 998 | transform: rotate(90deg); 999 | top: 7rem; 1000 | left: 9rem; 1001 | } 1002 | 1003 | #border-radius-bottom { 1004 | position: absolute; 1005 | bottom: -2rem; 1006 | left: 0; 1007 | transform: rotate(180deg); 1008 | margin: 6px auto; 1009 | } 1010 | 1011 | .gradient-border::before { 1012 | content: ''; 1013 | position: absolute; 1014 | inset: 0; 1015 | padding: 6px; 1016 | -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); 1017 | mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); 1018 | -webkit-mask-composite: xor; 1019 | mask-composite: exclude; 1020 | } 1021 | 1022 | .download-output > p { 1023 | font-size: 0.7rem; 1024 | } 1025 | 1026 | .download-btn { 1027 | display: flex; 1028 | justify-content: center; 1029 | align-items: center; 1030 | } 1031 | 1032 | .btn-dropdown-container, 1033 | .image-download { 1034 | display: block; 1035 | justify-content: center; 1036 | align-items: center; 1037 | position: relative; 1038 | gap: 20px; 1039 | } 1040 | 1041 | .btn-dropdown-container .css-tailwind, 1042 | .image-download .png-svg { 1043 | visibility: hidden; 1044 | position: absolute; 1045 | background-color: var(--primary-color); 1046 | color: var(--text-color); 1047 | transition: all 0.1s cubic-bezier(0.16, 1, 0.5, 1); 1048 | transform: translateY(0.5rem); 1049 | margin-top: 0.3rem; 1050 | opacity: 0; 1051 | border-radius: 5px; 1052 | cursor: pointer; 1053 | width: 150px; 1054 | padding: 0.3rem 0; 1055 | } 1056 | 1057 | .css-tailwind.show-css-tailwind, 1058 | .png-svg.show-png-svg { 1059 | visibility: visible; 1060 | transform: translateY(0rem); 1061 | opacity: 1; 1062 | } 1063 | 1064 | .dropdown-item { 1065 | display: flex; 1066 | justify-content: center; 1067 | align-items: center; 1068 | column-gap: 0.5rem; 1069 | padding: 0.3rem 0.3rem 0.3rem 0.2rem; 1070 | font-size: 12px; 1071 | } 1072 | 1073 | .dropdown-item:hover { 1074 | background-color: #0c49d2; 1075 | } 1076 | 1077 | .image-download > button:first-of-type { 1078 | margin-right: 1rem; 1079 | } 1080 | 1081 | input[type='range'] { 1082 | -webkit-appearance: none; 1083 | appearance: none; 1084 | background: transparent; 1085 | cursor: pointer; 1086 | } 1087 | 1088 | input[type='range']::-webkit-slider-runnable-track { 1089 | background: var(--secondary-color); 1090 | height: 0.5rem; 1091 | border-radius: 10px; 1092 | } 1093 | 1094 | input[type='range']::-moz-range-track { 1095 | background: var(--secondary-color); 1096 | height: 0.5rem; 1097 | border-radius: 10px; 1098 | } 1099 | 1100 | .border-range-inputs input[type='range']::-webkit-slider-runnable-track { 1101 | background-color: var(--primary-color); 1102 | } 1103 | 1104 | .border-range-inputs input[type='range']::-moz-range-track { 1105 | background-color: var(--primary-color); 1106 | } 1107 | 1108 | input[type='range']::-webkit-slider-thumb { 1109 | -webkit-appearance: none; 1110 | appearance: none; 1111 | background-color: var(--tertiary-color); 1112 | height: 1rem; 1113 | width: 1rem; 1114 | margin-top: -3.2px; 1115 | border-radius: 10px; 1116 | } 1117 | 1118 | input[type='range']::-moz-range-thumb { 1119 | border: none; 1120 | background-color: var(--tertiary-color); 1121 | height: 1rem; 1122 | width: 1rem; 1123 | border-radius: 10px; 1124 | } 1125 | 1126 | input[type='range']:focus { 1127 | outline: none; 1128 | } 1129 | 1130 | input[type='range']:focus::-webkit-slider-thumb, 1131 | input[type='range']:focus::-moz-range-thumb { 1132 | border: 1px solid #053a5f; 1133 | outline: 3px solid #053a5f; 1134 | outline-offset: 0.125rem; 1135 | } 1136 | 1137 | #preview-range:focus::-webkit-slider-thumb, 1138 | #preview-range:focus::-moz-range-thumb { 1139 | border: none; 1140 | outline: none; 1141 | outline-offset: none; 1142 | } 1143 | 1144 | #preview-range { 1145 | width: var(--preview-track-width); 1146 | } 1147 | 1148 | #preview-range::-webkit-slider-runnable-track { 1149 | height: var(--preview-track-height); 1150 | width: 100%; 1151 | border-radius: var(--preview-track-radius); 1152 | background-color: var(--preview-track-color); 1153 | } 1154 | 1155 | #preview-range::-moz-range-track { 1156 | height: var(--preview-track-height); 1157 | width: 100%; 1158 | border-radius: var(--preview-track-radius); 1159 | background-color: var(--preview-track-color); 1160 | } 1161 | 1162 | #preview-range::-webkit-slider-thumb { 1163 | background-color: var(--preview-thumb-color); 1164 | height: var(--preview-thumb-height); 1165 | width: var(--preview-thumb-width); 1166 | border-radius: var(--preview-thumb-radius); 1167 | } 1168 | 1169 | #preview-range::-moz-range-thumb { 1170 | background-color: var(--preview-thumb-color); 1171 | height: var(--preview-thumb-height); 1172 | width: var(--preview-thumb-width); 1173 | border-radius: var(--preview-thumb-radius); 1174 | } 1175 | 1176 | /* Transform page styles */ 1177 | 1178 | .transform-preview { 1179 | width: 200px; 1180 | height: 200px; 1181 | } 1182 | 1183 | #transform-degree { 1184 | display: block; 1185 | } 1186 | 1187 | /* Grid generators styles */ 1188 | 1189 | .grid-generators { 1190 | width: var(--input-width); 1191 | display: flex; 1192 | flex-direction: column; 1193 | align-items: center; 1194 | } 1195 | 1196 | .grid-generators-preview { 1197 | width: 100%; 1198 | height: 200px; 1199 | border: 1px solid white; 1200 | margin-top: 10px; 1201 | overflow: scroll; 1202 | } 1203 | 1204 | .grid-generators input[type='number'] { 1205 | border-radius: 5px; 1206 | height: 50px; 1207 | border: none; 1208 | background-color: var(--primary-color); 1209 | color: var(--text-color); 1210 | resize: none; 1211 | padding: 1rem; 1212 | margin-bottom: var(--spacing); 1213 | outline: none; 1214 | } 1215 | 1216 | .grid-generators input[type='number']::placeholder { 1217 | color: #ffffff; 1218 | } 1219 | 1220 | .grid-input-containers { 1221 | width: 100%; 1222 | display: grid; 1223 | grid-template-columns: 1fr 1fr; 1224 | gap: 10px; 1225 | border-radius: var(--spacing); 1226 | margin-top: calc(var(--spacing) + 10px); 1227 | } 1228 | 1229 | /* SCROLL GENERATOR STYLES */ 1230 | [data-content='scroll'] > .input { 1231 | gap: 10px; 1232 | } 1233 | 1234 | [data-content='scroll'] > .input input[type='number'] { 1235 | width: 100%; 1236 | padding: 8px; 1237 | border-color: var(--text-color); 1238 | } 1239 | 1240 | [data-content='scroll'] > .input input[type='number']::placeholder { 1241 | color: var(--text-color); 1242 | } 1243 | 1244 | #scroll-preview { 1245 | width: 300px; 1246 | height: 200px; 1247 | overflow-y: scroll; 1248 | border: 1px solid var(--text-color); 1249 | padding: 12px; 1250 | } 1251 | 1252 | /* FOOTER SECTION */ 1253 | 1254 | footer { 1255 | display: flex; 1256 | flex-direction: column; 1257 | justify-content: center; 1258 | font-size: 0.7rem; 1259 | position: relative; 1260 | top: 2rem; 1261 | color: var(--text-color); 1262 | } 1263 | 1264 | a { 1265 | color: var(--tertiary-color); 1266 | } 1267 | 1268 | @keyframes moveColor { 1269 | 0% { 1270 | background-position-x: -500%; 1271 | } 1272 | 1273 | 100% { 1274 | background-position-x: 500%; 1275 | } 1276 | } 1277 | 1278 | .eggy { 1279 | z-index: 10; 1280 | } 1281 | 1282 | @keyframes showOpenPreviousResultText { 1283 | from { 1284 | opacity: 0; 1285 | } 1286 | 1287 | to { 1288 | opacity: 1; 1289 | } 1290 | } 1291 | 1292 | @media screen and (max-width: 1010px) { 1293 | :root { 1294 | --input-width: 100%; 1295 | } 1296 | 1297 | nav ul > li:hover .tooltip { 1298 | display: none; 1299 | } 1300 | 1301 | .generator { 1302 | margin: 0.8rem auto; 1303 | } 1304 | 1305 | .input { 1306 | padding: 2rem; 1307 | } 1308 | 1309 | .btn-container { 1310 | justify-content: space-around; 1311 | } 1312 | } 1313 | 1314 | @media screen and (max-width: 650px) { 1315 | :root { 1316 | --output-width: 300px; 1317 | } 1318 | 1319 | .box-shadow-row label { 1320 | width: 50% !important; 1321 | } 1322 | 1323 | .divider { 1324 | width: 80%; 1325 | } 1326 | 1327 | nav { 1328 | position: absolute; 1329 | } 1330 | 1331 | .output, 1332 | .input { 1333 | padding: 2rem 0.3rem 2rem 0.3rem; 1334 | } 1335 | 1336 | .menu { 1337 | display: block; 1338 | position: absolute; 1339 | top: 1.7rem; 1340 | left: 1rem; 1341 | z-index: 999; 1342 | } 1343 | 1344 | .closed-nav { 1345 | left: -100%; 1346 | } 1347 | 1348 | .generator { 1349 | margin: 0.8rem auto; 1350 | } 1351 | 1352 | footer { 1353 | font-size: 1.3rem; 1354 | } 1355 | } 1356 | 1357 | @media screen and (max-width: 500px) { 1358 | .box-shadow-preview { 1359 | --box-size: 70px; 1360 | } 1361 | 1362 | .generator-tool-title { 1363 | top: 5%; 1364 | } 1365 | 1366 | .generator-tool-title h1 { 1367 | font-size: 1.4rem; 1368 | } 1369 | } 1370 | 1371 | @media only screen and (max-width: 1000px) { 1372 | .transform-container { 1373 | justify-content: center; 1374 | place-items: center; 1375 | flex-direction: column-reverse; 1376 | width: 100%; 1377 | } 1378 | 1379 | .transform-inputs-container { 1380 | margin-top: 50px; 1381 | } 1382 | } 1383 | @media screen and (max-width: 768px) { 1384 | .input-range-container { 1385 | display: flex; 1386 | padding: 5px; 1387 | } 1388 | .track-input, 1389 | .thumb-input { 1390 | padding: 2px; 1391 | width: 60%; 1392 | } 1393 | .checkbox-title { 1394 | display: flex; 1395 | align-items: center; 1396 | gap: 5px; 1397 | } 1398 | } 1399 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "strictFunctionTypes": false, 14 | "allowUnreachableCode": false, 15 | "noEmit": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "skipLibCheck": true 20 | }, 21 | "include": ["src"] 22 | } 23 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "out": "./docs", 3 | "excludeExternals": true, 4 | "readme": "none", 5 | "name": "Typedoc project reference documentation", 6 | "entryPointStrategy": "resolve", 7 | "entryPoints": ["./src/main.ts", "./src/pages/*", "./src/lib/*"], 8 | "tsconfig": "./tsconfig.json" 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vite'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | 4 | export default defineConfig({ 5 | plugins: [tsconfigPaths()], 6 | test: { 7 | globals: true, 8 | environment: 'jsdom', 9 | }, 10 | }); 11 | --------------------------------------------------------------------------------