├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── blank-issue.md │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── converters │ ├── colour-converter.js │ ├── epoch-time-converter.js │ ├── hex-to-filter.js │ ├── image-converter.js │ └── json-xml-converter.js ├── formatters │ └── code-formatter.js ├── icons │ ├── 256x256.jpg │ ├── avatar-placeholder.png │ ├── check.png │ ├── clock.png │ ├── close.png │ ├── code.png │ ├── colour.png │ ├── copy.png │ ├── file.png │ ├── image.png │ ├── json.png │ ├── link-icon.png │ ├── link.png │ ├── menu.png │ ├── notification.png │ ├── text.png │ └── warning.png ├── luna-framework.css ├── luna-main.js ├── style.css └── text │ ├── lorem-ipsum-generator.js │ └── word-counter.js ├── robots.txt ├── server.js ├── sitemap.xml └── views ├── converters ├── colour-converter.ejs ├── epoch-time-converter.ejs ├── hex-to-filter.ejs ├── image-converter.ejs └── json-xml-converter.ejs ├── footer.ejs ├── formatters └── code-formatter.ejs ├── header.ejs ├── index.ejs └── text ├── lorem-ipsum-generator.ejs └── word-counter.ejs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: THHamiltonSmith 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/blank-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Blank Issue 3 | about: Something that doesn't fit in with the other templates. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Enter your issue here... 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Answer all that apply, delete what isn't relevent.** 11 | --- 12 | 13 | - A clear and concise description of what your proposed feature is. *Ex. An Image converter to convert [...]* 14 | - Does your feature request relate to a problem? *Ex. I can't copy all text after it is formatted.* 15 | - Will you work on adding this feature *(Only answer if yes)* 16 |
17 | 18 | **Additional context** 19 | - Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '18 6 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules/ 3 | .env -------------------------------------------------------------------------------- /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 | thsskyenterprises@outlook.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Thomas Hamilton-Smith 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 | # WebDevHub - A place for developers 2 | 3 | [![GitHub issues](https://img.shields.io/github/issues/THHamiltonSmith/webdevhub)](https://github.com/THHamiltonSmith/webdevhub/issues) 4 | [![GitHub stars](https://img.shields.io/github/stars/THHamiltonSmith/webdevhub)](https://github.com/THHamiltonSmith/webdevhub/stargazers) 5 | [![GitHub license](https://img.shields.io/github/license/THHamiltonSmith/webdevhub)](https://github.com/THHamiltonSmith/webdevhub/blob/master/LICENSE) 6 | 7 | 8 | > WebDevHub is designed to be one central place for developers, that offers a variety of tools to help with any developing needs. This includes code formatters, file converters, image compressors etc. 9 | 10 | https://webdevhub.herokuapp.com/ 11 | 12 | A central hub with all the tools a developer might need makes it easier to focus on developing, rather then searching for different sites to find a working tool. WebDevHub eliminates this hassle. The site is open-source, designed to be made by the users. If there a feature you want to add, a bug you want to fix etc, simply open a pull request. 13 | 14 | --- 15 | 16 | ### Implemented Features 17 | - Hex to css `filter:` converter 18 | - Word Counter 19 | - Lorem Ipsum Generator 20 | - Code Formatter (HTML, CSS, JS, etc.) 21 | - JSON to XML converter. 22 | - Color Converter (Hex to RGB etc.) 23 | - Epoch Converter 24 | 25 | ### Planned Features 26 | - Image Converter *- UI implemented but needs server-side code to fix* 27 | - URL Shortner 28 | - File Compressors 29 | - Site/code templates. 30 | - Expansion to provide tools for other programming languages (C, C++, Python etc.) 31 | - CSS grid generator 32 | - JWT encoder / decoder 33 | - Link to Regex101 34 | - Colour palette generator from a base colour 35 | - Text & JSON diff 36 | - JS repl 37 | - ESLint & Prettier config generators 38 | - .MD formatter/preview 39 | 40 | 41 | If you like, you can fork the repo and create a pull request to contribute one of these features to the site. These are only some of the features planned to be added, and more will be listed here in the future. Check out the Projects page to see a more in-depth board of potential features to be added, or create a pull request and suggest your own feature. 42 | 43 | --- 44 | ### Frameworks Used 45 | 46 | - Node.js 47 | - Luna Framework (CSS) 48 | - jQuery 49 | - CodeMirror 50 | - js-beautify 51 | 52 |   53 | 54 | ## How to Contribute 55 | 56 | To contribute to the site: 57 | 58 | 1. Clone the repository via terminal or github desktop. 59 | 2. Run `npm i` to install all needed node packages. 60 | 3. In a terminal window opened in the main project directory, run `npm run devStart` to active nodemon, which will restart the test server whenever a change is made. The server can be acessed at `localhost:3000` 61 | 4. Create a new file in the `/views` directory titled `your-feature.ejs` and a new JS file in the `/public` directory titled `your-feature.js` in a relevent subfolder. 62 | 5. Add a new `app.get` request in the `server.js` file, as such: 63 | 64 | ```js 65 | app.get("/your-feature", (req, res) => { 66 | // Render the page with given paramaters. 67 | res.render("your-path/your-feature", { // The name of the .ejs file you created including the path 68 | title: "Your Feature", // The title of the webpage, usually the same as the feature name. 69 | }); 70 | }); 71 | ``` 72 | 73 | 6. Code your feature. 74 | 7. Add a link to the feature on the home page under a relevant subcategory. 75 | 8. (Optional) - Credit yourself at the bottom of your feature using this sample code. Have a look here for an example of how it is done. 76 | 77 | ```html 78 | 79 |
80 | Feature added by @your-username 81 | ``` 82 | 83 | 8. Create a pull request for your new feature. 84 | 85 | ### Rules/Guidelines for contributing 86 | 87 | - Try to follow the site's colour scheme. 88 | - Comment your code as you go and use readable variable names. Its hard to debug code when it looks like Latin. 89 | - Dont delete or change other people's code without a good explanation, or your pull-request wont be approved. 90 | 91 | > We want to make our documentation (including this README) the best it can be. If you have any suggestions, please open an issue or join our Discord for discussion. 92 | 93 |   94 | 95 | ## Screenshots 96 | 97 | Screenshot 2022-06-25 at 9 18 18 pm 98 | Screenshot 2022-06-28 at 11 51 51 pm 99 | Screenshot 2022-06-29 at 12 15 32 am 100 | 101 | 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webdevhub", 3 | "version": "1.0.0", 4 | "description": "A hub of tools and asset for web developers.", 5 | "main": "server.js", 6 | "scripts": { 7 | "devStart": "nodemon server.js --ext js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/THHamiltonSmith/webdevhub.git" 13 | }, 14 | "keywords": [ 15 | "webdev", 16 | "web", 17 | "development", 18 | "web", 19 | "dev", 20 | "tools", 21 | "development", 22 | "tools", 23 | "webdevhub" 24 | ], 25 | "author": "Sky Enterprises", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/THHamiltonSmith/webdevhub/issues" 29 | }, 30 | "homepage": "https://github.com/THHamiltonSmith/webdevhub#readme", 31 | "dependencies": { 32 | "dotenv": "^16.0.1", 33 | "ejs": "^3.1.8", 34 | "express": "^4.18.1", 35 | "mongoose": "^6.4.4", 36 | "nodemon": "^2.0.15", 37 | "xml-js": "^1.6.11" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/converters/colour-converter.js: -------------------------------------------------------------------------------- 1 | // colour-converter.js 2 | 3 | const inputsContainer = document.querySelector(".colour-inputs"); 4 | 5 | // For each of the list elements that contain an input 6 | for (var i = 0; i < inputsContainer.children.length; i++) { 7 | const li = inputsContainer.children[i]; 8 | 9 | // And an event listener to detect change in the input. 10 | li.children[1].addEventListener('input', function (evt) { 11 | 12 | // If the HEX length is valid or anohter input is changed, send the colour to the server to convert. 13 | if (this.id == "hex" && this.value.length == 7 || this.id == "hex" && this.value.length == 4 || this.id != "hex") { 14 | // prettier-ignore 15 | fetch("/convert-colour", { 16 | method: "POST", 17 | headers: { 18 | "Content-Type": "application/json", 19 | }, 20 | body: JSON.stringify({ 21 | focusedInputValue: this.value, 22 | focusedInputType: this.id 23 | }), 24 | }).then((res) => res.json()).then((res) => { 25 | // HTTP 301 response 26 | const types = ["hex", "rgb", "hsl", "hwb"] 27 | 28 | // Check each type and append any relevent styling. 29 | for (var type in types) { 30 | if (document.activeElement.id != types[type]) { 31 | 32 | if (types[type] == "hex") { 33 | document.getElementById("hex").value = "#" + res.colourObj[types[type].toString()] 34 | } 35 | else if (types[type] == "rgb") { 36 | document.getElementById("rgb").value = "rgb(" + res.colourObj[types[type].toString()] + ")" 37 | } 38 | else if (types[type] == "hsl") { 39 | document.getElementById("hsl").value = "hsl(" + res.colourObj[types[type].toString()] + ")" 40 | } 41 | else if (types[type] == "hwb") { 42 | document.getElementById("hwb").value = "hwb(" + res.colourObj[types[type].toString()] + ")" 43 | } 44 | } 45 | } 46 | 47 | // Update the colour block. 48 | const colourBlock = document.querySelector(".colour-block"); 49 | colourBlock.style.backgroundColor = "#" + res.colourObj.hex 50 | }) 51 | .catch(function (err) { 52 | // console.log(err + " url: " + url); 53 | }); 54 | } 55 | }) 56 | } 57 | 58 | // Copy text to clipboard 59 | function copyText(btnObj) { 60 | const text = btnObj.parentNode.children[1].value; 61 | navigator.clipboard.writeText(text); 62 | 63 | // Displays text `Copied!` for 2 seconds 64 | const popupText = btnObj.children[1] 65 | popupText.style.opacity = 1; 66 | setTimeout(function() { 67 | popupText.style.opacity = 0 68 | }, 2000) 69 | } -------------------------------------------------------------------------------- /public/converters/epoch-time-converter.js: -------------------------------------------------------------------------------- 1 | const epoch_time = document.getElementById("epoch-time"); 2 | const converted_time = document.getElementById("converted-time"); 3 | const popupText = document.getElementById("popup-text"); 4 | 5 | function convertTime() { 6 | let date = new Date(epoch_time.value * 1000); 7 | date = date.toLocaleString(); 8 | converted_time.value = date; 9 | } 10 | 11 | function copyText() { 12 | navigator.clipboard.writeText(converted_time.value); 13 | 14 | // Displays text `Copied!` for 2 seconds 15 | popupText.style.opacity = 1; 16 | setTimeout(function () { 17 | popupText.style.opacity = 0; 18 | }, 2000); 19 | } 20 | -------------------------------------------------------------------------------- /public/converters/hex-to-filter.js: -------------------------------------------------------------------------------- 1 | // hexToFilter.js 2 | 3 | "use strict"; 4 | 5 | class Color { 6 | constructor(r, g, b) { 7 | this.set(r, g, b); 8 | } 9 | 10 | toString() { 11 | return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`; 12 | } 13 | 14 | set(r, g, b) { 15 | this.r = this.clamp(r); 16 | this.g = this.clamp(g); 17 | this.b = this.clamp(b); 18 | } 19 | 20 | hueRotate(angle = 0) { 21 | angle = (angle / 180) * Math.PI; 22 | const sin = Math.sin(angle); 23 | const cos = Math.cos(angle); 24 | 25 | this.multiply([ 26 | 0.213 + cos * 0.787 - sin * 0.213, 27 | 0.715 - cos * 0.715 - sin * 0.715, 28 | 0.072 - cos * 0.072 + sin * 0.928, 29 | 0.213 - cos * 0.213 + sin * 0.143, 30 | 0.715 + cos * 0.285 + sin * 0.14, 31 | 0.072 - cos * 0.072 - sin * 0.283, 32 | 0.213 - cos * 0.213 - sin * 0.787, 33 | 0.715 - cos * 0.715 + sin * 0.715, 34 | 0.072 + cos * 0.928 + sin * 0.072, 35 | ]); 36 | } 37 | 38 | grayscale(value = 1) { 39 | this.multiply([ 40 | 0.2126 + 0.7874 * (1 - value), 41 | 0.7152 - 0.7152 * (1 - value), 42 | 0.0722 - 0.0722 * (1 - value), 43 | 0.2126 - 0.2126 * (1 - value), 44 | 0.7152 + 0.2848 * (1 - value), 45 | 0.0722 - 0.0722 * (1 - value), 46 | 0.2126 - 0.2126 * (1 - value), 47 | 0.7152 - 0.7152 * (1 - value), 48 | 0.0722 + 0.9278 * (1 - value), 49 | ]); 50 | } 51 | 52 | sepia(value = 1) { 53 | this.multiply([0.393 + 0.607 * (1 - value), 0.769 - 0.769 * (1 - value), 0.189 - 0.189 * (1 - value), 0.349 - 0.349 * (1 - value), 0.686 + 0.314 * (1 - value), 0.168 - 0.168 * (1 - value), 0.272 - 0.272 * (1 - value), 0.534 - 0.534 * (1 - value), 0.131 + 0.869 * (1 - value)]); 54 | } 55 | 56 | saturate(value = 1) { 57 | this.multiply([0.213 + 0.787 * value, 0.715 - 0.715 * value, 0.072 - 0.072 * value, 0.213 - 0.213 * value, 0.715 + 0.285 * value, 0.072 - 0.072 * value, 0.213 - 0.213 * value, 0.715 - 0.715 * value, 0.072 + 0.928 * value]); 58 | } 59 | 60 | multiply(matrix) { 61 | const newR = this.clamp(this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]); 62 | const newG = this.clamp(this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]); 63 | const newB = this.clamp(this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]); 64 | this.r = newR; 65 | this.g = newG; 66 | this.b = newB; 67 | } 68 | 69 | brightness(value = 1) { 70 | this.linear(value); 71 | } 72 | contrast(value = 1) { 73 | this.linear(value, -(0.5 * value) + 0.5); 74 | } 75 | 76 | linear(slope = 1, intercept = 0) { 77 | this.r = this.clamp(this.r * slope + intercept * 255); 78 | this.g = this.clamp(this.g * slope + intercept * 255); 79 | this.b = this.clamp(this.b * slope + intercept * 255); 80 | } 81 | 82 | invert(value = 1) { 83 | this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255); 84 | this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255); 85 | this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255); 86 | } 87 | 88 | hsl() { 89 | // Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA. 90 | const r = this.r / 255; 91 | const g = this.g / 255; 92 | const b = this.b / 255; 93 | const max = Math.max(r, g, b); 94 | const min = Math.min(r, g, b); 95 | let h, 96 | s, 97 | l = (max + min) / 2; 98 | 99 | if (max === min) { 100 | h = s = 0; 101 | } else { 102 | const d = max - min; 103 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 104 | switch (max) { 105 | case r: 106 | h = (g - b) / d + (g < b ? 6 : 0); 107 | break; 108 | 109 | case g: 110 | h = (b - r) / d + 2; 111 | break; 112 | 113 | case b: 114 | h = (r - g) / d + 4; 115 | break; 116 | } 117 | h /= 6; 118 | } 119 | 120 | return { 121 | h: h * 100, 122 | s: s * 100, 123 | l: l * 100, 124 | }; 125 | } 126 | 127 | clamp(value) { 128 | if (value > 255) { 129 | value = 255; 130 | } else if (value < 0) { 131 | value = 0; 132 | } 133 | return value; 134 | } 135 | } 136 | 137 | class Solver { 138 | constructor(target, baseColor) { 139 | this.target = target; 140 | this.targetHSL = target.hsl(); 141 | this.reusedColor = new Color(0, 0, 0); 142 | } 143 | 144 | solve() { 145 | const result = this.solveNarrow(this.solveWide()); 146 | return { 147 | values: result.values, 148 | loss: result.loss, 149 | filter: this.css(result.values), 150 | }; 151 | } 152 | 153 | solveWide() { 154 | const A = 5; 155 | const c = 15; 156 | const a = [60, 180, 18000, 600, 1.2, 1.2]; 157 | 158 | let best = { loss: Infinity }; 159 | for (let i = 0; best.loss > 25 && i < 3; i++) { 160 | const initial = [50, 20, 3750, 50, 100, 100]; 161 | const result = this.spsa(A, a, c, initial, 1000); 162 | if (result.loss < best.loss) { 163 | best = result; 164 | } 165 | } 166 | return best; 167 | } 168 | 169 | solveNarrow(wide) { 170 | const A = wide.loss; 171 | const c = 2; 172 | const A1 = A + 1; 173 | const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1]; 174 | return this.spsa(A, a, c, wide.values, 500); 175 | } 176 | 177 | spsa(A, a, c, values, iters) { 178 | const alpha = 1; 179 | const gamma = 0.16666666666666666; 180 | 181 | let best = null; 182 | let bestLoss = Infinity; 183 | const deltas = new Array(6); 184 | const highArgs = new Array(6); 185 | const lowArgs = new Array(6); 186 | 187 | for (let k = 0; k < iters; k++) { 188 | const ck = c / Math.pow(k + 1, gamma); 189 | for (let i = 0; i < 6; i++) { 190 | deltas[i] = Math.random() > 0.5 ? 1 : -1; 191 | highArgs[i] = values[i] + ck * deltas[i]; 192 | lowArgs[i] = values[i] - ck * deltas[i]; 193 | } 194 | 195 | const lossDiff = this.loss(highArgs) - this.loss(lowArgs); 196 | for (let i = 0; i < 6; i++) { 197 | const g = (lossDiff / (2 * ck)) * deltas[i]; 198 | const ak = a[i] / Math.pow(A + k + 1, alpha); 199 | values[i] = fix(values[i] - ak * g, i); 200 | } 201 | 202 | const loss = this.loss(values); 203 | if (loss < bestLoss) { 204 | best = values.slice(0); 205 | bestLoss = loss; 206 | } 207 | } 208 | return { values: best, loss: bestLoss }; 209 | 210 | function fix(value, idx) { 211 | let max = 100; 212 | if (idx === 2 /* saturate */) { 213 | max = 7500; 214 | } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) { 215 | max = 200; 216 | } 217 | 218 | if (idx === 3 /* hue-rotate */) { 219 | if (value > max) { 220 | value %= max; 221 | } else if (value < 0) { 222 | value = max + (value % max); 223 | } 224 | } else if (value < 0) { 225 | value = 0; 226 | } else if (value > max) { 227 | value = max; 228 | } 229 | return value; 230 | } 231 | } 232 | 233 | loss(filters) { 234 | // Argument is array of percentages. 235 | const color = this.reusedColor; 236 | color.set(0, 0, 0); 237 | 238 | color.invert(filters[0] / 100); 239 | color.sepia(filters[1] / 100); 240 | color.saturate(filters[2] / 100); 241 | color.hueRotate(filters[3] * 3.6); 242 | color.brightness(filters[4] / 100); 243 | color.contrast(filters[5] / 100); 244 | 245 | const colorHSL = color.hsl(); 246 | return Math.abs(color.r - this.target.r) + Math.abs(color.g - this.target.g) + Math.abs(color.b - this.target.b) + Math.abs(colorHSL.h - this.targetHSL.h) + Math.abs(colorHSL.s - this.targetHSL.s) + Math.abs(colorHSL.l - this.targetHSL.l); 247 | } 248 | 249 | css(filters) { 250 | function fmt(idx, multiplier = 1) { 251 | return Math.round(filters[idx] * multiplier); 252 | } 253 | return `filter: invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%);`; 254 | } 255 | } 256 | 257 | function hexToRgb(hex) { 258 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 259 | const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 260 | hex = hex.replace(shorthandRegex, (m, r, g, b) => { 261 | return r + r + g + g + b + b; 262 | }); 263 | 264 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 265 | return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null; 266 | } 267 | 268 | function executeFilter() { 269 | const rgb = hexToRgb($("input.target").val()); 270 | if (rgb.length !== 3) { 271 | alert("Invalid format!"); 272 | return; 273 | } 274 | 275 | const color = new Color(rgb[0], rgb[1], rgb[2]); 276 | const solver = new Solver(color); 277 | const result = solver.solve(); 278 | 279 | let lossMsg; 280 | if (result.loss < 1) { 281 | lossMsg = "This is a perfect result."; 282 | } else if (result.loss < 5) { 283 | lossMsg = "The is close enough."; 284 | } else if (result.loss < 15) { 285 | lossMsg = "The color is somewhat off. Consider running it again."; 286 | } else { 287 | lossMsg = "The color is extremely off. Run it again!"; 288 | } 289 | 290 | $(".realPixel").css("background-color", color.toString()); 291 | $(".filterPixel").attr("style", result.filter); 292 | $(".filterDetail").text(result.filter); 293 | $(".lossDetail").html(`Loss: ${result.loss.toFixed(1)}. ${lossMsg}`); 294 | } 295 | -------------------------------------------------------------------------------- /public/converters/image-converter.js: -------------------------------------------------------------------------------- 1 | const fileInput = document.getElementById("loadfile"); 2 | const previewImage = document.getElementById("preview-image"); 3 | const previewDefaultText = document.getElementById("preview-text"); 4 | let currImgFormat = ""; 5 | let upload = false; 6 | let conversion = false; 7 | 8 | // preview Image 9 | fileInput.addEventListener("change", function(){ 10 | const file = this.files[0]; 11 | currImgFormat = file.type.slice(6); 12 | 13 | if (file) { 14 | upload = true; 15 | const reader = new FileReader(); 16 | 17 | previewDefaultText.style.display = "none"; 18 | previewImage.style.display = "block"; 19 | 20 | reader.readAsDataURL(file); 21 | 22 | // when load file is completed 23 | reader.addEventListener("load", function(){ 24 | previewImage.setAttribute("src", this.result); 25 | }) 26 | 27 | 28 | } else { 29 | previewDefaultText.style.display = null; 30 | previewImage.style.display = null; 31 | previewImage.setAttribute("src", ""); 32 | } 33 | }) 34 | 35 | 36 | const submit = document.getElementById("submit"); 37 | const format = document.getElementById("format"); 38 | const error = document.getElementById("error"); 39 | const errorText = document.getElementById("error-text"); 40 | const success = document.getElementById("success"); 41 | const successText = document.getElementById("success-text"); 42 | const download = document.getElementById("download"); 43 | 44 | // convert format 45 | submit.addEventListener("click", function(){ 46 | 47 | // file not uploaded 48 | if (!upload) { 49 | errorText.innerHTML = "Upload an image to continue"; 50 | error.style.display = "flex"; 51 | success.style.display = null; 52 | download.style.display = null; 53 | return; 54 | 55 | // no format selected 56 | } else if (format.value == "none") { 57 | errorText.innerHTML = "Please select a format"; 58 | error.style.display = "flex"; 59 | success.style.display = null; 60 | download.style.display = null; 61 | return; 62 | 63 | // selected same format as uploaded image 64 | } else if (format.value == currImgFormat) { 65 | errorText.innerHTML = "Selected format should not be same as original"; 66 | error.style.display = "flex"; 67 | success.style.display = null; 68 | download.style.display = null; 69 | return; 70 | 71 | } else { 72 | errorText.innerHTML = null; 73 | error.style.display = null; 74 | 75 | // set flag after conversion 76 | conversion = true; 77 | 78 | // after successful conversion 79 | if (conversion) { 80 | successText.innerHTML = `Successfully converted to .${format.value}`; 81 | success.style.display = "flex"; 82 | download.style.display = "block"; 83 | download.setAttribute("href", "dataURL of converted image here"); 84 | } 85 | } 86 | }) -------------------------------------------------------------------------------- /public/converters/json-xml-converter.js: -------------------------------------------------------------------------------- 1 | // json-xml-converter.js 2 | 3 | // References 4 | const codeEditorObj = document.getElementById("code-editor"); 5 | const formattedCodeObj = document.getElementById("code-formatted"); 6 | const formatSlector = document.getElementById("format-selector"); 7 | const popupText = document.getElementById("popup-text"); 8 | 9 | // Get the code editors and the active code type. 10 | var codeEditor; 11 | var codeFormatted; 12 | var activeCodeType = "xml"; 13 | 14 | // On page load initialise the text areas. 15 | window.addEventListener("load", () => { 16 | 17 | // Initialise text areas. 18 | codeEditor = CodeMirror.fromTextArea(codeEditorObj, { 19 | mode: "text/javascript", 20 | theme: "lesser-dark", 21 | scrollbarStyle: "simple", 22 | lineNumbers: true, 23 | }); 24 | codeFormatted = CodeMirror.fromTextArea(formattedCodeObj, { 25 | mode: "text/xml", 26 | theme: "lesser-dark", 27 | scrollbarStyle: "simple", 28 | lineNumbers: true, 29 | }); 30 | }); 31 | 32 | // Format the code and append it to the textbox. 33 | function convertData() { 34 | // Get the code to format 35 | const toConvertCode = codeEditor.getDoc().getValue(); 36 | 37 | // prettier-ignore 38 | fetch("/convert-json-data", { 39 | method: "POST", 40 | headers: { 41 | "Content-Type": "application/json", 42 | }, 43 | body: JSON.stringify({ 44 | toConvertCode: toConvertCode, 45 | conversionType: activeCodeType, 46 | }), 47 | }).then((res) => res.json()).then((res) => { 48 | // HTTP 301 response 49 | // If the referral code exists, reset the page, if node, show an error 50 | document.getElementById("error-message-container").style.display = "none"; 51 | codeFormatted.getDoc().setValue(res.result); 52 | }) 53 | .catch(function (err) { 54 | document.getElementById("error-message-container").style.display = "block"; 55 | // console.log(err + " url: " + url); 56 | }); 57 | } 58 | 59 | // Change the format modes of the code inputs when a different convert type is selected. 60 | function changeModes() { 61 | const newMode = formatSlector.value; 62 | 63 | // Depending on what conversion type was selected, change the format type of the code editors. 64 | if (newMode == "javascript") { 65 | codeEditor.setOption("mode", "text/xml"); 66 | codeFormatted.setOption("mode", "text/javascript"); 67 | 68 | document.getElementById("header-1").children[0].innerHTML = "XML"; 69 | document.getElementById("header-2").children[1].innerHTML = "JSON"; 70 | } else if (newMode == "xml") { 71 | codeEditor.setOption("mode", "text/javascript"); 72 | codeFormatted.setOption("mode", "text/xml"); 73 | 74 | document.getElementById("header-1").children[0].innerHTML = "JSON"; 75 | document.getElementById("header-2").children[1].innerHTML = "XML"; 76 | } 77 | 78 | // Set the active code type. 79 | activeCodeType = newMode; 80 | } 81 | 82 | // Copy text to clipboard 83 | function copyText() { 84 | const text = codeFormatted.getDoc().getValue(); 85 | navigator.clipboard.writeText(text); 86 | 87 | // Displays text `Copied!` for 2 seconds 88 | popupText.style.opacity = 1; 89 | setTimeout(function() { 90 | popupText.style.opacity = 0 91 | }, 2000) 92 | } -------------------------------------------------------------------------------- /public/formatters/code-formatter.js: -------------------------------------------------------------------------------- 1 | // code-formatter.js 2 | 3 | // References 4 | const codeEditorObj = document.getElementById("code-editor"); 5 | const formattedCodeObj = document.getElementById("code-formatted"); 6 | const formatSlector = document.getElementById("format-selector"); 7 | const popupText = document.getElementById("popup-text"); 8 | 9 | // Get the code editors and the active code type. 10 | var codeEditor; 11 | var codeFormatted; 12 | var activeCodeType = "javascript"; 13 | 14 | // On page load initialise the text areas. 15 | window.addEventListener("load", () => { 16 | // Check if the URL contains a '#' (means that they clicked on a specific language on home page) 17 | // Then set the modes of the code editors and language to be that language. 18 | if (window.location.href.includes("#")) { 19 | const lastHash = window.location.href.lastIndexOf("#"); 20 | const result = window.location.href.substring(lastHash + 1); 21 | 22 | formatSlector.value = result; 23 | activeCodeType = result; 24 | } 25 | 26 | const newMode = "text/" + activeCodeType; 27 | 28 | // Initialise text areas. 29 | codeEditor = CodeMirror.fromTextArea(codeEditorObj, { 30 | mode: newMode, 31 | theme: "lesser-dark", 32 | scrollbarStyle: "simple", 33 | lineNumbers: true, 34 | }); 35 | codeFormatted = CodeMirror.fromTextArea(formattedCodeObj, { 36 | mode: newMode, 37 | theme: "lesser-dark", 38 | scrollbarStyle: "simple", 39 | lineNumbers: true, 40 | }); 41 | }); 42 | 43 | // Format the code and append it to the textbox. 44 | function formatCode() { 45 | // Get the code to format 46 | const toFormatCode = codeEditor.getDoc().getValue(); 47 | var formattedCode = ""; 48 | 49 | // Format the code depending on the language and set it to the formatted box. 50 | if (activeCodeType == "html") { 51 | formattedCode = html_beautify(toFormatCode); 52 | } else if (activeCodeType == "css") { 53 | formattedCode = css_beautify(toFormatCode); 54 | } else if (activeCodeType == "javascript") { 55 | formattedCode = js_beautify(toFormatCode); 56 | } 57 | 58 | // Set the formatted value to the formatted code. 59 | codeFormatted.getDoc().setValue(formattedCode); 60 | } 61 | 62 | // Change the format modes of the code. 63 | function changeModes() { 64 | const newMode = formatSlector.value; 65 | 66 | // Set the format type of the code editors to the new mode. 67 | codeEditor.setOption("mode", "text/" + newMode); 68 | codeFormatted.setOption("mode", "text/" + newMode); 69 | 70 | // Set the active code type. 71 | activeCodeType = newMode; 72 | } 73 | 74 | // Copy text to clipboard 75 | function copyText() { 76 | const text = codeFormatted.getDoc().getValue(); 77 | navigator.clipboard.writeText(text); 78 | 79 | // Displays text `Copied!` for 2 seconds 80 | popupText.style.opacity = 1; 81 | setTimeout(function() { 82 | popupText.style.opacity = 0 83 | }, 2000) 84 | } -------------------------------------------------------------------------------- /public/icons/256x256.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/256x256.jpg -------------------------------------------------------------------------------- /public/icons/avatar-placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/avatar-placeholder.png -------------------------------------------------------------------------------- /public/icons/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/check.png -------------------------------------------------------------------------------- /public/icons/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/clock.png -------------------------------------------------------------------------------- /public/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/close.png -------------------------------------------------------------------------------- /public/icons/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/code.png -------------------------------------------------------------------------------- /public/icons/colour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/colour.png -------------------------------------------------------------------------------- /public/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/copy.png -------------------------------------------------------------------------------- /public/icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/file.png -------------------------------------------------------------------------------- /public/icons/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/image.png -------------------------------------------------------------------------------- /public/icons/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/json.png -------------------------------------------------------------------------------- /public/icons/link-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/link-icon.png -------------------------------------------------------------------------------- /public/icons/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/link.png -------------------------------------------------------------------------------- /public/icons/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/menu.png -------------------------------------------------------------------------------- /public/icons/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/notification.png -------------------------------------------------------------------------------- /public/icons/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/text.png -------------------------------------------------------------------------------- /public/icons/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THHamiltonSmith/webdevhub/8afbc1dfbce40a6e512b566c840567f42ece8f6e/public/icons/warning.png -------------------------------------------------------------------------------- /public/luna-framework.css: -------------------------------------------------------------------------------- 1 | /* 2 | /* Main Styling 3 | /* * * * * * * * * */ 4 | 5 | body { 6 | position: relative; 7 | overflow-x: hidden; 8 | scroll-behavior: smooth; 9 | background-color: #141414; 10 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; 11 | color: #e7e7e7; 12 | margin: 0; 13 | max-width: 100%; 14 | } 15 | footer { 16 | position: absolute; 17 | bottom: 0; 18 | padding: 30px; 19 | width: calc(100% - 60px); 20 | background-color: #222; 21 | text-align: center; 22 | font-size: 15px; 23 | } 24 | footer span { 25 | line-height: 22px; 26 | } 27 | footer a { 28 | color: #aaa; 29 | transition: 0.2s; 30 | } 31 | footer a:hover { 32 | color: #ccc; 33 | } 34 | footer .md { 35 | margin-left: 5px; 36 | margin-right: 5px; 37 | } 38 | 39 | /* Inputs and Form Elements */ 40 | button, input, textarea { 41 | border: none; 42 | transition: 0.2s ease-in-out; 43 | color: #e7e7e7; 44 | } 45 | button, input[type=button], input[type=submit], input[type=reset] { 46 | padding: 15px; 47 | padding-left: 35px; 48 | padding-right: 35px; 49 | border-radius: 5px; 50 | width: initial; 51 | background-color: #2a2a2a; 52 | } 53 | button:hover, input[type=button]:hover { 54 | background-color: #353535; 55 | } 56 | input[type=submit] { 57 | background-color: #008cff; 58 | cursor: pointer; 59 | } 60 | input[type=submit]:hover { 61 | background-color: #00a2ff; 62 | } 63 | button, input[type=button] { 64 | cursor: pointer; 65 | } 66 | input:focus, textarea:focus, select:focus { 67 | outline: none; 68 | } 69 | 70 | /* DOM Elements */ 71 | h1 { 72 | font-weight: 600; 73 | } 74 | p { 75 | line-height: 1.5; 76 | } 77 | a { 78 | color: #008cff; 79 | transition: 0.2s; 80 | text-decoration: none; 81 | line-height: 1.5; 82 | } 83 | a:hover { 84 | color: #00aaff; 85 | } 86 | .arrow-link:after { 87 | background-image: url('icons/open-icon.png'); 88 | background-repeat: no-repeat; 89 | background-size: 11px 11px; 90 | display: inline-block; 91 | margin-left: 8px; 92 | width: 11px; 93 | height: 11px; 94 | content:" "; 95 | } 96 | hr { 97 | border: 1px solid #777; 98 | } 99 | 100 | /* Index-Main */ 101 | .index-main { 102 | padding: 60px; 103 | padding-top: 70px; 104 | padding-bottom: 100px; 105 | margin: auto; 106 | max-width: 1300px; 107 | } 108 | .index-main p { 109 | color: #999; 110 | font-size: 18px; 111 | } 112 | .index-main p a { 113 | color: #cbcbcb; 114 | border-bottom: 1px solid #ddd 115 | } 116 | .index-main p a:hover { 117 | color: #ddd; 118 | } 119 | .container { 120 | --bs-gutter-x: 1.5rem; 121 | --bs-gutter-y: 0; 122 | width: 100%; 123 | padding-right: calc(var(--bs-gutter-x) * .5); 124 | padding-left: calc(var(--bs-gutter-x) * .5); 125 | margin-right: auto; 126 | margin-left: auto; 127 | } 128 | 129 | /* Navbar */ 130 | .navbar { 131 | position: absolute; 132 | display: flex; 133 | align-items: center; 134 | padding: 2px; 135 | padding-left: 3rem; 136 | background-color: #090909; 137 | height: 52px; 138 | width: 100%; 139 | } 140 | .nav-container { 141 | display: flex; 142 | width: 100%; 143 | } 144 | .nav-centered { 145 | margin: auto; 146 | max-width: 1400px; 147 | height: 54px; 148 | } 149 | .navbar-ul { 150 | display: flex; 151 | flex-wrap: wrap; 152 | gap: 30px; 153 | } 154 | .navbar-ul li { 155 | display: inline-flex; 156 | list-style-type: none; 157 | font-size: 15px; 158 | } 159 | .navbar-toggler { 160 | display: none; 161 | } 162 | .nav-brand { 163 | display: flex; 164 | align-items: center; 165 | font-weight: 600; 166 | } 167 | .nav-brand img { 168 | margin-right: 6px; 169 | } 170 | .nav-brand a { 171 | margin-top: 1px; 172 | color: #008cff; 173 | transition: 0.2s; 174 | } 175 | .nav-link { 176 | color: #e7e7e7; 177 | transition: 0.2s; 178 | } 179 | .nav-link:hover { 180 | color: #aaa; 181 | } 182 | .nav-icons { 183 | width: 25px; 184 | height: 25px; 185 | } 186 | .active { 187 | font-weight: 500; 188 | } 189 | 190 | /* Dropdown */ 191 | .dropdown { 192 | position: relative; 193 | } 194 | .dropdown-toggle::after { 195 | display: inline-block; 196 | margin-left: 0.255em; 197 | vertical-align: 0.255em; 198 | content: ""; 199 | border-top: 0.3em solid; 200 | border-right: 0.3em solid transparent; 201 | border-bottom: 0; 202 | border-left: 0.3em solid transparent; 203 | } 204 | .dropdown-menu { 205 | display: none !important; 206 | position: absolute; 207 | padding: 0; 208 | padding-top: 10px; 209 | padding-bottom: 10px; 210 | top: 40px; 211 | border-radius: 6px; 212 | width: 160px; 213 | background-color: #1a1a1a; 214 | box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); 215 | } 216 | .show { 217 | display: block !important; 218 | } 219 | .dropdown-menu li { 220 | display: block; 221 | } 222 | .dropdown-menu li a { 223 | display: block; 224 | padding: 7.25px; 225 | padding-left: 12px; 226 | padding-right: 12px; 227 | } 228 | .dropdown-item { 229 | color: #ccc !important; 230 | } 231 | .dropdown-item:hover { 232 | background-color: #222; 233 | transition: 0.2s; 234 | } 235 | .dropdown-divider { 236 | margin-top: 7px; 237 | margin-bottom: 7px; 238 | border: 0; 239 | border-top: 1px solid #444; 240 | } 241 | 242 | /* Profile Dropdown */ 243 | .profile-dropdown { 244 | position: absolute; 245 | opacity: 0; 246 | pointer-events: none; 247 | top: 52px; 248 | right: 110px; 249 | width: 200px; 250 | border: 2px solid #333; 251 | border-radius: 5px; 252 | background-color: #1a1a1a; 253 | transition: all 0.2s linear; 254 | } 255 | .profile-dropdown li { 256 | display: flex; 257 | align-items: center; 258 | padding: 12px; 259 | padding-left: 21px; 260 | font-size: 14px; 261 | } 262 | .profile-dropdown li:hover { 263 | cursor: pointer; 264 | background-color: #333; 265 | } 266 | .profile-dropdown li:first-child { 267 | border-radius: 3px 3px 0 0; 268 | } 269 | .profile-dropdown li img{ 270 | margin-right: 10px; 271 | margin-top: 1px; 272 | width: 20px; 273 | } 274 | .profile-dropdown-logout { 275 | border-top: 2px solid #333; 276 | font-weight: 600; 277 | border-radius: 0 0 2px 2px; 278 | } 279 | 280 | /* Custom Navbar Properties */ 281 | .collapsible { 282 | position: relative; 283 | overflow: hidden; 284 | display: flex; 285 | align-items: center; 286 | flex-basis: 100%; 287 | padding: 0 18px; 288 | padding-top: 0px !important; 289 | padding-right: 4rem; 290 | background-color: #090909; 291 | } 292 | .expanded { 293 | max-height: 1000px !important; 294 | overflow: visible !important; 295 | } 296 | .nav-right { 297 | margin-left: auto; 298 | padding-right: 3rem; 299 | } 300 | 301 | /* Colours (Mainly for Inputs) */ 302 | .input-blue { 303 | background-color: #008cff !important; 304 | } 305 | .input-blue:hover { 306 | background-color: #00a2ff !important; 307 | } 308 | .input-red { 309 | background-color: #f74343 !important; 310 | } 311 | .input-red:hover { 312 | background-color: #e75555 !important; 313 | } 314 | 315 | /* Modal Styling */ 316 | .dashboard-box { 317 | position: relative; 318 | overflow-y: auto; 319 | margin: 30px; 320 | padding: 35px; 321 | border-radius: 8px; 322 | width: 550px; 323 | max-height: 700px; 324 | font-size: 14px; 325 | background-color: #141414; 326 | } 327 | .dashboard-modal { 328 | z-index: 1000; 329 | position: absolute; 330 | display: none; 331 | justify-content: center; 332 | align-items: center; 333 | width: 100%; 334 | height: 100%; 335 | background-color: rgba(0, 0, 0, 0.9); 336 | } 337 | .dashboard-modal input { 338 | width: 100%; 339 | } 340 | .dashboard-modal input[type=submit] { 341 | width: initial; 342 | } 343 | .close-modal-button { 344 | position: absolute; 345 | top: 25px; 346 | right: 25px; 347 | padding: 5px; 348 | border-radius: 50%; 349 | width: 20px !important; 350 | background-color: #242424; 351 | transition: 0.2s; 352 | } 353 | .close-modal-button:hover { 354 | background-color: #2c2c2c; 355 | } 356 | .dashboard-box h2 { 357 | margin-top: 0px; 358 | color: #eee; 359 | } 360 | .dashboard-box p { 361 | color: #666; 362 | margin-bottom: 20px; 363 | } 364 | 365 | /* Inputs and Forms */ 366 | .form-container { 367 | display: flex; 368 | flex-wrap: wrap; 369 | align-items: flex-start; 370 | gap: 10px; 371 | max-width: 710px; 372 | } 373 | .form-header { 374 | flex-basis: 100%; 375 | } 376 | .form-small { 377 | color: #999; 378 | } 379 | .form-column { 380 | display: flex; 381 | flex-direction: column; 382 | flex: 1; 383 | } 384 | .form-inline { 385 | display: flex; 386 | gap: 10px; 387 | justify-content: space-between; 388 | } 389 | .floating-control { 390 | display: inline-block; 391 | margin-bottom: 10px; 392 | transition: background-color 0.2s ease; 393 | flex: 1; 394 | } 395 | .floating-control:last-child { 396 | margin-bottom: 0px; 397 | } 398 | .floating-input, .floating-control select, .floating-control input[type=date] { 399 | padding: 1.8rem 1rem 0.6rem; 400 | background-color: #2a2a2a; 401 | transition: border-color 0.2s ease; 402 | width: 100%; 403 | border-radius: 0.375rem; 404 | font-size: 1rem; 405 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 406 | } 407 | .floating-control input[type=date] { 408 | background-image: none !important; 409 | padding-right: 1rem !important; 410 | } 411 | ::-webkit-calendar-picker-indicator { 412 | filter: invert(1); 413 | } 414 | .floating-textarea { 415 | position: relative; 416 | } 417 | .floating-textarea textarea { 418 | padding-top: 1.6rem; 419 | height: 100px; 420 | resize: none; 421 | } 422 | .floating-control textarea + label { 423 | position: absolute; 424 | top: 57px; 425 | } 426 | .floating-textarea span { 427 | position: absolute; 428 | right: 10px; 429 | bottom: 7px; 430 | font-size: 12px; 431 | color: #888; 432 | } 433 | .floating-input { 434 | max-width: 100%; 435 | } 436 | .floating-input:focus, .form-select:focus { 437 | box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%); 438 | } 439 | .floating-input::placeholder { 440 | color: rgba(0, 0, 0, 0); 441 | } 442 | .form-select { 443 | display: block; 444 | border: none; 445 | padding-top: 1.625rem; 446 | padding-bottom: 0.45rem; 447 | padding-left: 1rem; 448 | padding-right: 2rem !important; 449 | width: auto; 450 | height: calc(3.5rem + 2px); 451 | background-image: url("data:image/svg+xml,") !important; 452 | background-repeat: no-repeat; 453 | background-position: right 0.75rem center; 454 | background-size: 16px 12px; 455 | color: #fff; 456 | -webkit-appearance: none; 457 | -moz-appearance: none; 458 | appearance: none; 459 | } 460 | .floating-label { 461 | display: block; 462 | position: absolute; 463 | max-height: 0; 464 | pointer-events: none; 465 | } 466 | .floating-label::before { 467 | position: relative; 468 | display: inline-block; 469 | transform-origin: left top; 470 | content: attr(data-content); 471 | top: 2px; 472 | left: 1rem; 473 | color: #aaa; 474 | transition: transform 0.2s ease; 475 | } 476 | .floating-input:placeholder-shown + .floating-label::before { 477 | transform: translate3d(0, -2.4rem, 0) scale3d(1, 1, 1); 478 | opacity: 1; 479 | } 480 | .floating-label::before, .floating-input:focus + .floating-label::before { 481 | transform: translate3d(0, -3.12rem, 0) scale3d(0.82, 0.82, 1); 482 | opacity: 0.7; 483 | } 484 | .floating-input:focus + .floating-textarea label::before { 485 | transform: translate3d(0, -2.6rem, 0) scale3d(0.82, 0.82, 1) !important; 486 | } 487 | .floating-input:focus + .floating-label::before { 488 | opacity: 0.7; 489 | } 490 | .visually-hidden { 491 | position: absolute; 492 | clip: rect(1px, 1px, 1px, 1px); 493 | height: 1px; 494 | width: 1px; 495 | } 496 | 497 | /* Pres & Code Blocks */ 498 | pre { 499 | padding-left: 30px; 500 | border-radius: 5px; 501 | max-width: 700px; 502 | background-color: #222831; 503 | overflow-x: scroll; 504 | } 505 | 506 | /* CSS Styles */ 507 | .pre-title { 508 | color: rgb(207, 179, 121); 509 | } 510 | .pre-variable { 511 | color: rgb(157, 220, 254); 512 | } 513 | .pre-variable-value { 514 | color: rgb(182, 206, 168); 515 | } 516 | .pre-variable-special { 517 | color: rgb(221, 220, 170); 518 | } 519 | .pre-comment { 520 | color: rgb(107, 153, 85); 521 | } 522 | 523 | 524 | 525 | /* Media Querys */ 526 | @media (max-width: 1000px) { 527 | 528 | /* Navbar */ 529 | .navbar { 530 | padding-left: 20px; 531 | width: calc(100% - 22px); 532 | background-color: #090909; 533 | } 534 | .navbar-toggler { 535 | display: block; 536 | position: absolute; 537 | } 538 | .nav-brand { 539 | margin-left: auto; 540 | margin-right: auto; 541 | } 542 | .collapsible { 543 | position: absolute; 544 | overflow: hidden; 545 | top: 56px; 546 | margin-left: -2.9rem; 547 | max-height: 0; 548 | width: calc(100% - 3.4rem); 549 | transition: all 0.5s ease-in-out; 550 | } 551 | .navbar ul { 552 | display: block; 553 | margin-top: 10px; 554 | } 555 | .nav-right { 556 | display: flex !important; 557 | align-self: flex-start; 558 | padding-right: 0rem; 559 | } 560 | .navbar ul li { 561 | display: block; 562 | margin-bottom: 15px; 563 | } 564 | .dropdown-menu { 565 | margin-top: 0px !important; 566 | } 567 | .dropdown-menu li { 568 | margin-bottom: 0px !important; 569 | } 570 | 571 | /* Modals */ 572 | .dashboard-box { 573 | width: calc(550 - 30); 574 | } 575 | .dashboard-box p { 576 | margin-bottom: 15px; 577 | } 578 | .floating-control { 579 | margin-bottom: 1rem; 580 | } 581 | } 582 | @media (max-width: 800px) { 583 | .form-container { 584 | flex-direction: column; 585 | } 586 | .form-column { 587 | width: 100%; 588 | } 589 | .floating-control { 590 | margin-bottom: 10px; 591 | } 592 | } 593 | 594 | /* Container Max Width */ 595 | 596 | @media (min-width: 576px) { 597 | .container { 598 | max-width: 540px; 599 | } 600 | } 601 | @media (min-width: 768px) { 602 | .container { 603 | max-width: 720px; 604 | } 605 | } 606 | @media (min-width: 992px) { 607 | .container { 608 | max-width: 960px; 609 | } 610 | } 611 | @media (min-width: 1200px) { 612 | .container { 613 | max-width: 1140px; 614 | } 615 | } 616 | @media (min-width: 1400px) { 617 | .container { 618 | max-width: 1320px; 619 | } 620 | } -------------------------------------------------------------------------------- /public/luna-main.js: -------------------------------------------------------------------------------- 1 | // luna-main.js 2 | 3 | const navbarToggler = document.getElementById("navbar-toggler"); 4 | const profileDropdownObj = document.getElementById("profile-dropdown"); 5 | 6 | const dropdowns = document.getElementsByClassName("dropdown-toggle"); 7 | 8 | for (var i = 0; i < dropdowns.length; i++) { 9 | dropdowns[i].addEventListener("click", (e) => { 10 | const linkedDropdown = document.getElementById(e.target.getAttribute("nav-target")); 11 | 12 | if (linkedDropdown.classList.contains("show")) { 13 | linkedDropdown.classList.remove("show"); 14 | } else { 15 | linkedDropdown.classList.add("show") 16 | } 17 | }) 18 | } 19 | 20 | // When the navbarToggler is clicked, show and hide the navbar content. 21 | navbarToggler.addEventListener("click", function () { 22 | // 23 | const content = this.nextElementSibling.children[1]; 24 | const dropdownMenus = document.getElementsByClassName("dropdown-menu") 25 | 26 | // If the collapsable div doesnt have inline maxHeight styling, show it. If it does, hide it. 27 | if (content.classList.contains("expanded")) { 28 | content.classList.remove("expanded"); 29 | 30 | // Hide any dropdowns 31 | for (var i = 0; i < dropdownMenus.length; i++) { 32 | dropdownMenus[i].style.opacity = 0; 33 | } 34 | } else { 35 | content.classList.add("expanded"); 36 | 37 | // Show any dropdowns 38 | for (var i = 0; i < dropdownMenus.length; i++) { 39 | dropdownMenus[i].style.opacity = 1; 40 | } 41 | } 42 | }); -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | /* Main HTML Styling */ 2 | body { 3 | overflow-x: hidden; 4 | position: relative; 5 | background-color: #171e27; 6 | } 7 | footer { 8 | background-color: #25303e; 9 | } 10 | .navbar { 11 | background-color: #14151a; 12 | } 13 | .collapsible { 14 | background-color: #14151a; 15 | } 16 | 17 | 18 | .index-main { 19 | padding: 40px; 20 | padding-top: 55px; 21 | padding-bottom: 120px !important; 22 | min-height: calc(100vh - 80px); 23 | max-width: 1300px; 24 | margin: auto; 25 | } 26 | .link-icon { 27 | filter: invert(47%) sepia(8%) saturate(0%) hue-rotate(297deg) brightness(97%) contrast(93%); 28 | margin-bottom: 0px !important; 29 | width: 20px !important; 30 | height: 20px !important; 31 | } 32 | .credit-box { 33 | text-align: center; 34 | } 35 | .credit-hr { 36 | margin-top: 30px; 37 | width: 100%; 38 | } 39 | .credit-link { 40 | margin-top: 10px; 41 | color: #999; 42 | } 43 | .credit-link a { 44 | color: #bbb; 45 | transition: 0.2s; 46 | } 47 | .credit-link a:hover { 48 | color: #ccc; 49 | } 50 | .active { 51 | font-weight: 500; 52 | } 53 | 54 | /* Header */ 55 | .main-header { 56 | margin-top: 70px; 57 | text-align: center; 58 | } 59 | .main-header h1 { 60 | font-size: 32px; 61 | } 62 | .main-header span { 63 | color: #888; 64 | } 65 | .main-header hr { 66 | margin-top: 50px; 67 | border: 2px solid #555; 68 | } 69 | .disclaimer { 70 | margin: auto; 71 | margin-top: 20px; 72 | margin-bottom: 20px; 73 | width: 65%; 74 | line-height: 20px; 75 | font-size: 15px; 76 | color: #888; 77 | } 78 | .disclaimer a { 79 | color: #999; 80 | transition: 0.2s; 81 | } 82 | .disclaimer a:hover { 83 | color: #bbb; 84 | } 85 | 86 | /* Feature Buttons */ 87 | .feature-container { 88 | display: flex; 89 | flex-flow: row wrap; 90 | justify-content: center; 91 | } 92 | .feature-container a { 93 | display: flex; 94 | align-items: center; 95 | padding: 20px; 96 | padding-left: 20px; 97 | padding-right: 20px; 98 | margin: 10px; 99 | border-radius: 10px; 100 | width: 245px; 101 | background-color: #303e51; 102 | color: #eee; 103 | text-decoration: none; 104 | } 105 | .feature-container a:hover { 106 | background-color: #3d526f; 107 | box-shadow: none; 108 | transition: 0.2s; 109 | } 110 | .feature-container a span { 111 | font-weight: 600; 112 | color: #eee !important; 113 | } 114 | .feature-container a p { 115 | color: #aaa; 116 | margin-top: 8px !important; 117 | margin-bottom: 0px; 118 | line-height: 21px; 119 | font-size: 16px; 120 | } 121 | .feature-container a code, .colour-converter .hex-to-filter-disclaimer code { 122 | padding: 3px; 123 | padding-left: 6px; 124 | padding-right: 6px; 125 | border-radius: 5px; 126 | background-color: #4c6e9e; 127 | font-size: 15px; 128 | } 129 | .feature-container a p code { 130 | display: inline; 131 | background-color: transparent; 132 | } 133 | .link-img-container { 134 | display: inline-block; 135 | background-color: #18202a; 136 | padding: 8px; 137 | border-radius: 5px; 138 | width: 26px; 139 | height: 26px; 140 | margin-right: 8px; 141 | } 142 | .link-img-container img { 143 | width: 100%; 144 | filter: invert(100%); 145 | } 146 | .functional { 147 | box-shadow: 0 0 0 1px #00a2ff; 148 | } 149 | 150 | /* Main Feature Styling */ 151 | .main-content-container { 152 | padding: 30px; 153 | margin: 0 auto; 154 | max-width: 1200px; 155 | } 156 | .feature-main-header { 157 | margin-top: 20px; 158 | } 159 | 160 | /* Feature Rows */ 161 | .feature-row { 162 | position: relative; 163 | margin-top: 50px; 164 | overflow: hidden; 165 | } 166 | .feature-row:after { 167 | content: ''; 168 | pointer-events: none; 169 | position: absolute; 170 | top: 76px; 171 | left: 0; 172 | right: 0; 173 | bottom: 20px; 174 | background-image: linear-gradient(to right, transparent, 98%, #171e27); 175 | } 176 | .feature-row h2 { 177 | margin-bottom: 10px; 178 | } 179 | .feature-row span:nth-child(2) { 180 | color: #aaa; 181 | } 182 | .feature-row hr { 183 | margin-top: 10px; 184 | } 185 | .feature-row hr:last-child { 186 | border: 1px solid #333; 187 | } 188 | .feature-row-content { 189 | flex-flow: row nowrap; 190 | justify-content: flex-start; 191 | align-items: center; 192 | padding: 10px; 193 | padding-left: 0; 194 | padding-right: 10px; 195 | padding-bottom: 5px; 196 | overflow-x: auto; 197 | cursor: pointer; 198 | } 199 | .feature-row-type { 200 | padding: 0; 201 | } 202 | .feature-row-type a:first-child { 203 | display: block; 204 | } 205 | .feature-row-type a:first-child p { 206 | margin-top: 12px; 207 | } 208 | .feature-row-content::-webkit-scrollbar { 209 | width: 20px; 210 | box-shadow: inset 0 0 5px #303e51; 211 | border-radius: 10px; 212 | } 213 | .feature-row-content::-webkit-scrollbar-thumb { 214 | border-radius: 10px; 215 | box-shadow: inset 0 0 5px #3a4d66; 216 | } 217 | .feature-row-content::-webkit-scrollbar-thumb:hover { 218 | box-shadow: inset 0 0 5px #41648e; 219 | } 220 | .feature-row-content-divider { 221 | margin-left: 6px; 222 | margin-right: 6px; 223 | min-height: 100%; 224 | width: 4px; 225 | background-color: #555; 226 | align-self: stretch; 227 | } 228 | .feature-row-content a { 229 | flex-shrink: 0; 230 | height: 0%; 231 | padding-bottom: 23px; 232 | margin-bottom: 14px; 233 | } 234 | 235 | 236 | /* Word Counter */ 237 | .word-counter textarea, .word-counter-header, .word-counter-footer { 238 | padding: 10px; 239 | padding-left: 16px; 240 | border-radius: 10px; 241 | width: 100%; 242 | color: #ddd; 243 | resize: none; 244 | } 245 | .word-counter { 246 | display: flex; 247 | padding-top: 25px; 248 | width: 100%; 249 | } 250 | .word-counter-container { 251 | flex-basis: 85%; 252 | padding-right: 50px; 253 | } 254 | .word-counter textarea { 255 | padding-top: 15px; 256 | background-color: #273240; 257 | box-shadow: 0 8px 6px -2px #14181e; 258 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 259 | } 260 | .word-counter textarea:focus { 261 | box-shadow: 0 0 0 1px #00a2ff; 262 | } 263 | .word-counter-header { 264 | margin-bottom: 15px; 265 | background-color: #5775d6; 266 | box-shadow: 0 8px 6px -2px #11182c; 267 | color: #e7e7e7; 268 | } 269 | .word-counter-footer { 270 | margin-top: 8px; 271 | padding: 8px; 272 | padding-left: 15px; 273 | padding-right: 13px; 274 | font-weight: 500; 275 | width: 100%; 276 | background-color: #1f2630; 277 | box-shadow: 0 8px 6px -2px #161b21; 278 | } 279 | .word-counter-sidebar { 280 | border-radius: 10px; 281 | height: 0%; 282 | width: 320px; 283 | background-color: #324560; 284 | box-shadow: 0 8px 6px -2px #11161e; 285 | } 286 | .word-counter-sidebar ul { 287 | margin: 0; 288 | padding-left: 0; 289 | } 290 | .word-counter-sidebar-header { 291 | border-radius: 10px 10px 0 0; 292 | background-color: #242c38; 293 | font-weight: 600; 294 | font-size: 16px !important; 295 | border-bottom: none !important; 296 | } 297 | .word-counter-sidebar li { 298 | display: flex; 299 | justify-content: space-between; 300 | padding: 8px; 301 | padding-left: 12px; 302 | padding-right: 16px; 303 | border-bottom: 2px solid #4f688d; 304 | font-size: 15px; 305 | } 306 | .word-counter-sidebar li:last-child { 307 | border: none; 308 | } 309 | .word-counter-sidebar li span { 310 | padding-left: 8px; 311 | padding-right: 8px; 312 | border-radius: 10px; 313 | background-color: #475d7e; 314 | font-size: 13px; 315 | line-height: 23px; 316 | } 317 | 318 | 319 | /* Lorem Ipsum Generator */ 320 | .lorem-ipsum p { 321 | margin-bottom: 20px; 322 | text-align: justify !important; 323 | } 324 | .lorem-ipsum input { 325 | padding: 5px; 326 | border-radius: 5px; 327 | background-color: #29384c; 328 | color: #e7e7e7; 329 | } 330 | .lorem-ipsum input[type=number] { 331 | width: 45px; 332 | text-align: center; 333 | } 334 | .lorem-ipsum input:focus { 335 | box-shadow: 0 0 0 1px #00a2ff; 336 | } 337 | .lorem-ipsum input[type=button] { 338 | padding-left: 12px; 339 | padding-right: 12px; 340 | margin-left: 10px; 341 | } 342 | /* Chrome, Safari, Edge, Opera */ 343 | .lorem-ipsum input::-webkit-outer-spin-button, 344 | .lorem-ipsum input::-webkit-inner-spin-button { 345 | -webkit-appearance: none; 346 | margin: 0; 347 | } 348 | .lorem-ipsum-controls { 349 | display: flex; 350 | align-items: center; 351 | padding: 15px; 352 | padding-left: 25px; 353 | margin-top: 25px; 354 | margin-bottom: 15px; 355 | border-radius: 10px; 356 | background-color: #5775d6; 357 | box-shadow: 0 8px 6px -2px #11182c; 358 | color: #e7e7e7; 359 | } 360 | .lorem-ipsum-controls span { 361 | margin-right: 12px; 362 | font-size: 18px; 363 | font-weight: 500; 364 | } 365 | .lorem-ipsum-controls .word-count { 366 | position: absolute; 367 | right: 15px; 368 | } 369 | #paragraph-container { 370 | padding: 30px; 371 | padding-left: 40px; 372 | padding-right: 40px; 373 | padding-bottom: 20px; 374 | margin-top: 10px; 375 | min-height: 60px; 376 | border-radius: 10px; 377 | background-color: #273240; 378 | box-shadow: 0 8px 6px -2px #14181e; 379 | } 380 | #paragraph-container p { 381 | color: #ddd; 382 | } 383 | 384 | /* Hex to Filter */ 385 | .pixel { 386 | display: inline-block; 387 | background-color: #000; 388 | width: 50px; 389 | height: 50px; 390 | } 391 | .filterDetail { 392 | position: absolute; 393 | white-space: nowrap; 394 | margin-left: 0px !important; 395 | font-family: "Consolas", "Menlo", "Ubuntu Mono", monospace; 396 | } 397 | .lossDetail { 398 | margin-top: 40px; 399 | } 400 | .hex-to-filter { 401 | position: relative; 402 | display: flex; 403 | margin-top: 25px; 404 | } 405 | .hex-to-filter button, .hex-to-filter input { 406 | padding: 8px; 407 | padding-left: 10px; 408 | padding-right: 10px; 409 | border-radius: 5px; 410 | } 411 | .hex-to-filter button { 412 | margin-bottom: 20px; 413 | background-color: #008cff; 414 | color: #fff; 415 | font-weight: 500; 416 | } 417 | .hex-to-filter input { 418 | margin-right: 5px; 419 | width: 100px; 420 | background-color: #ccc; 421 | color: black; 422 | } 423 | .hex-to-filter label { 424 | margin-right: 10px; 425 | font-size: 18px; 426 | font-weight: 500; 427 | } 428 | .hex-to-filter-main-content { 429 | position: relative; 430 | flex-basis: 50%; 431 | } 432 | .hex-to-filter-main-content code { 433 | background-color: #354966; 434 | margin-left: 5px; 435 | padding: 3px; 436 | padding-left: 8px; 437 | padding-right: 8px; 438 | border-radius: 5px; 439 | } 440 | .hex-to-filter-main-content p { 441 | line-height: 28px; 442 | } 443 | .hex-to-filter-disclaimer { 444 | flex-basis: 50%; 445 | padding: 30px; 446 | align-self: flex-start; 447 | border-radius: 15px; 448 | background-color: #303e51; 449 | color: #aaa; 450 | } 451 | .hex-to-filter-disclaimer-code { 452 | padding: 1px; 453 | padding-left: 6px; 454 | padding-right: 6px; 455 | margin-left: 3px; 456 | margin-right: 3px; 457 | border-radius: 3px; 458 | font-size: 15px; 459 | background-color: #222831; 460 | } 461 | .hex-to-filter-disclaimer p { 462 | margin-top: 0px; 463 | line-height: 20px; 464 | } 465 | .hex-to-filter-disclaimer pre { 466 | padding-left: 30px; 467 | margin-bottom: 0px; 468 | background-color: #222831; 469 | overflow-x: scroll; 470 | } 471 | .pre-title { 472 | color: rgb(207, 179, 121); 473 | } 474 | .pre-variable { 475 | color: rgb(157, 220, 254); 476 | } 477 | .pre-variable-value { 478 | color: rgb(182, 206, 168); 479 | } 480 | .pre-variable-special { 481 | color: rgb(221, 220, 170); 482 | } 483 | .pre-comment { 484 | color: rgb(107, 153, 85); 485 | } 486 | 487 | /* Image Converter */ 488 | .image-converter { 489 | display: flex; 490 | flex-direction: column; 491 | align-items: center; 492 | justify-content: center; 493 | padding: 10px; 494 | } 495 | 496 | .image-converter-loadfile { 497 | background-color: #14151a; 498 | } 499 | 500 | .image-converter-preview-container { 501 | background-color: #25303e; 502 | border-radius: 8px; 503 | border: 3px solid #14151a; 504 | width: 50%; 505 | min-height: 300px; 506 | margin-top: 15px; 507 | display: flex; 508 | justify-content: center; 509 | align-items: center; 510 | } 511 | 512 | .image-converter-preview-image { 513 | display: none; 514 | width: 100%; 515 | } 516 | 517 | .image-converter-convert { 518 | display: flex; 519 | align-items: center; 520 | } 521 | 522 | .image-converter-convert label { 523 | margin: 12px; 524 | } 525 | 526 | .image-converter-submit { 527 | background-color: #14151a; 528 | color: white; 529 | margin: 12px; 530 | } 531 | 532 | .image-converter-error img { 533 | width: 25px; 534 | height: 25px; 535 | margin-right: 10px; 536 | padding: auto; 537 | } 538 | 539 | .image-converter-success img { 540 | width: 18px; 541 | height: 18px; 542 | margin-right: 10px; 543 | padding: auto; 544 | } 545 | 546 | .image-converter-error, .image-converter-success { 547 | display: none; /* will change to display flex based on conditions */ 548 | align-items: center; 549 | margin: 10px; 550 | } 551 | 552 | .image-converter-download { 553 | display: none; 554 | margin: 20px; 555 | } 556 | 557 | .image-converter-download button { 558 | width: 400px; 559 | height: 50px; 560 | background-color: #5775d6; 561 | color: white; 562 | } 563 | .disclaimer-box { 564 | margin: auto; 565 | margin-top: 20px; 566 | margin-bottom: 0px; 567 | padding: 20px; 568 | padding-left: 30px; 569 | padding-right: 30px; 570 | border-radius: 20px; 571 | width: 80%; 572 | background-color: #243142; 573 | box-shadow: 0 8px 6px -2px #14181e; 574 | font-size: 16px !important; 575 | } 576 | 577 | /* Code Formatter */ 578 | .code-formatter-container { 579 | display: flex; 580 | width: 100%; 581 | gap: 20px; 582 | } 583 | .code-formatter-container textarea { 584 | width: 100%; 585 | background-color: #262626; 586 | } 587 | .formatter-container { 588 | flex: 1; /* formerly flex: 1 0 auto; */ 589 | } 590 | .formatter-container span { 591 | font-weight: 600; 592 | } 593 | .formatter-container div { 594 | max-width: 590px !important; 595 | } 596 | .formatter-container button, .converter-container .epoch-copy { 597 | position: relative; 598 | background-color:#242426; 599 | border-radius: 5px; 600 | padding: 5px; 601 | width: 30px; 602 | height: 33px; 603 | left: 95%; 604 | bottom: 94%; /* 86% <==> 90% */ 605 | } 606 | .converter-container .epoch-copy { 607 | width: 33px; 608 | left: 94.2%; 609 | bottom: 81%; 610 | } 611 | .converter-container .epoch-copy-text { 612 | position: absolute; 613 | left: 80%; 614 | bottom: 86%; 615 | } 616 | .formatter-container img { 617 | width: 100%; 618 | height: 100%; 619 | } 620 | #copy { 621 | box-shadow: none; 622 | } 623 | .formatter-popup { 624 | z-index: 100; 625 | position: relative; 626 | display: inline-block; 627 | pointer-events: none; 628 | text-align: center; 629 | background-color: #262626; 630 | color: white; 631 | padding-top: 2px; 632 | opacity: 0; 633 | bottom: 96%; 634 | left: 75%; 635 | width: 74px; 636 | height: 23px; 637 | border-radius: 6px; 638 | } 639 | .code-formatter-controls { 640 | text-align: center; 641 | margin-top: 15px; 642 | line-height: 40px; 643 | } 644 | .code-formatter-controls input[type=button] { 645 | margin-left: 40px; 646 | background-color: #008cff; 647 | color: #e7e7e7; 648 | transition: 0.2s; 649 | } 650 | .code-formatter-controls input, .code-formatter-controls select { 651 | margin-left: 10px; 652 | } 653 | .code-formatter-controls input[type=button]:hover { 654 | background-color: #00a2ff; 655 | } 656 | .code-formatter-header { 657 | display: flex; 658 | margin-top: 12px; 659 | margin-bottom: 12px; 660 | gap: 20px; 661 | } 662 | .code-formatter-header span { 663 | flex-basis: 50%; 664 | font-weight: 600; 665 | } 666 | .CodeMirror { 667 | padding-top: 4px; 668 | } 669 | .CodeMirror-scroll::-webkit-scrollbar { 670 | display: none !important; 671 | } 672 | .CodeMirror-gutters { 673 | padding-right: 6px; 674 | } 675 | .CodeMirror-line { 676 | padding-left: 12px !important; 677 | } 678 | .CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical { 679 | background-color: #243142; 680 | } 681 | .CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div { 682 | background-color: #324560; 683 | border: 1px solid #273240; 684 | } 685 | .CodeMirror-gutter-filler, .CodeMirror-scrollbar-filler { 686 | background-color: #1f2630; 687 | } 688 | 689 | /* Error Message */ 690 | #error-message-container { 691 | display: none; 692 | text-align: center; 693 | padding: 20px; 694 | margin-bottom: 30px; 695 | background-color: #0381e8; 696 | border-radius: 5px; 697 | } 698 | #error-message-container span { 699 | color: #e7e7e7; 700 | font-size: 15px; 701 | } 702 | 703 | /* Colour Converter */ 704 | .colour-converter { 705 | display: flex; 706 | flex-wrap: wrap; 707 | gap: 40px; 708 | margin-top: 30px; 709 | } 710 | .flex-break { 711 | display: none; 712 | flex-basis: 100%; 713 | height: 0; 714 | } 715 | .colour-inputs { 716 | display: flex; 717 | flex-direction: column; 718 | gap: 10px; 719 | } 720 | .colour-inputs li { 721 | position: relative; 722 | list-style-type: none; 723 | max-width: 314.5px; 724 | } 725 | .colour-inputs li label { 726 | display: inline-block; 727 | margin-top: 10px; 728 | margin-bottom: 10px; 729 | width: 50px; 730 | font-weight: 600; 731 | } 732 | .colour-inputs li input { 733 | background-color: #2a303d; 734 | color: #e7e7e7; 735 | padding: 3px; 736 | padding-left: 9px; 737 | width: 240px; 738 | height: 30px; 739 | font-size: 16px; 740 | } 741 | .colour-inputs li button { 742 | position: absolute; 743 | right: 0; 744 | height: 46px; 745 | background-color: transparent; 746 | padding: 6px; 747 | } 748 | .colour-inputs li button img { 749 | margin-top: 2px; 750 | margin-right: 4px; 751 | width: 18px; 752 | height: 18px; 753 | transition: 0.1s; 754 | } 755 | .colour-inputs li button img:hover { 756 | filter: invert(39%) sepia(37%) saturate(0%) hue-rotate(152deg) brightness(93%) contrast(86%); 757 | } 758 | .colour-inputs li button span { 759 | position: absolute; 760 | top: 13px; 761 | left: -70px; 762 | background-color: transparent; 763 | color: #aaa; 764 | } 765 | .colour-block-container h4 { 766 | margin-top: 0px; 767 | margin-bottom: 15px; 768 | } 769 | .colour-block { 770 | width: 100px; 771 | height: 100px; 772 | background-color: #ffffff; 773 | } 774 | .colour-converter .hex-to-filter-disclaimer { 775 | flex: 1; 776 | } 777 | .colour-converter .hex-to-filter-disclaimer p { 778 | display: inline; 779 | } 780 | .colour-converter .hex-to-filter-disclaimer code { 781 | background-color: #2a303d; 782 | margin-left: 2px; 783 | margin-right: 2px; 784 | } 785 | .colour-converter .hex-to-filter-disclaimer a { 786 | color: #ccc; 787 | transition: 0.2s; 788 | border-bottom: 1px solid #ccc; 789 | } 790 | .colour-converter .hex-to-filter-disclaimer a:hover { 791 | color: #e7e7e7; 792 | } 793 | 794 | /* Media Querys */ 795 | @media (max-width: 1400px) { 796 | .formatter-container { 797 | position: relative; 798 | } 799 | .formatter-container button { 800 | position: absolute; 801 | bottom: 94%; 802 | left: auto; 803 | right: 0; 804 | } 805 | .converter-container .epoch-copy { 806 | position: absolute; 807 | bottom: 72%; 808 | left: auto; 809 | right: 0; 810 | } 811 | .formatter-popup { 812 | position: absolute; 813 | left: auto; 814 | right: 40px; 815 | bottom: 95%; 816 | } 817 | .converter-container .epoch-copy-text { 818 | position: absolute; 819 | left: auto; 820 | right: 40px; 821 | bottom: 80%; 822 | } 823 | } 824 | @media (max-width: 1250px) { 825 | .hex-to-filter { 826 | flex-direction: column-reverse; 827 | font-size: 14px; 828 | } 829 | .hex-to-filter-disclaimer { 830 | padding: 20px !important; 831 | width: calc(100% - 40px); 832 | margin-bottom: 30px; 833 | } 834 | } 835 | @media (max-width: 1000px) { 836 | .filterDetail { 837 | position: static; 838 | white-space: inherit; 839 | } 840 | .lossDetail { 841 | margin-top: 20px; 842 | } 843 | .image-converter-preview-container { 844 | width: 70%; 845 | } 846 | } 847 | @media (max-width: 800px) { 848 | .disclaimer { 849 | width: 80%; 850 | } 851 | .flex-break { 852 | margin-top: -20px; 853 | display: block !important; 854 | } 855 | .index-main { 856 | padding: 30px; 857 | } 858 | .main-content-container { 859 | padding: 10px; 860 | padding-top: 30px; 861 | } 862 | .word-counter { 863 | flex-direction: column; 864 | gap: 20px; 865 | } 866 | .word-counter-container { 867 | padding-right: 26px; 868 | } 869 | .word-counter-sidebar { 870 | width: 100%; 871 | } 872 | 873 | /* Code Formatters */ 874 | .code-formatter-container { 875 | flex-direction: column; 876 | } 877 | .formatter-container div { 878 | max-width: 100% !important; 879 | } 880 | 881 | .image-converter-preview-container { 882 | width: 80%; 883 | } 884 | } 885 | @media (max-width: 400px) { 886 | .colour-converter { 887 | flex-direction: column; 888 | } 889 | .index-main { 890 | padding: 20px; 891 | } 892 | .image-converter-preview-container { 893 | width: 95%; 894 | } 895 | } 896 | 897 | .epoch-converter-header { 898 | display: flex; 899 | margin-top: 12px; 900 | margin-bottom: 12px; 901 | gap: 20px; 902 | } 903 | 904 | .epoch-converter-header span { 905 | flex-basis: 50%; 906 | font-weight: 600; 907 | text-align: center; 908 | } 909 | 910 | .epoch-converter-controls { 911 | margin-top: 15px; 912 | text-align: center; 913 | } 914 | 915 | .epoch-converter-controls input[type="button"] { 916 | background-color: #008cff; 917 | color: #e7e7e7; 918 | transition: 0.2s; 919 | } 920 | 921 | .converter-container { 922 | flex: 1; 923 | position: relative; 924 | } 925 | 926 | .converter-container div { 927 | max-width: 590px !important; 928 | } 929 | 930 | .converter-container textarea { 931 | color: white; 932 | font-size: large; 933 | resize: none; 934 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 935 | padding: 8px; 936 | width: calc(100% - 16px); 937 | } 938 | 939 | .converter-container button { 940 | background-color: #242426; 941 | border-radius: 5px; 942 | padding: 5px; 943 | width: 30px; 944 | height: 33px; 945 | position: relative; 946 | left: 101%; 947 | } 948 | -------------------------------------------------------------------------------- /public/text/lorem-ipsum-generator.js: -------------------------------------------------------------------------------- 1 | // loremIpsumGenerator.js 2 | 3 | (function () { 4 | "use strict"; 5 | 6 | /** 7 | * LoremIpsum constructor 8 | * 9 | * @type {Function} 10 | */ 11 | window.LoremIpsum = function () { 12 | // pass 13 | }; 14 | 15 | /** 16 | * LoremIpsum prototype 17 | * 18 | * @type {Object} 19 | */ 20 | window.LoremIpsum.prototype = { 21 | /** 22 | * Possible words 23 | * 24 | * @type {Array} 25 | */ 26 | // prettier-ignore 27 | _words: [ "a", "ac", "accumsan", "ad", "adipiscing", "aenean", "aenean", "aliquam", "aliquam", 28 | "aliquet", "amet", "ante", "aptent", "arcu", "at", "auctor", "augue", "bibendum", "blandit", "class", 29 | "commodo", "condimentum", "congue", "consectetur", "consequat", "conubia", "convallis", "cras", 30 | "cubilia", "curabitur", "curabitur", "curae", "cursus", "dapibus", "diam", "dictum", "dictumst", 31 | "dolor", "donec", "donec", "dui", "duis", "egestas", "eget", "eleifend", "elementum", "elit", "enim", 32 | "erat", "eros", "est", "et", "etiam", "etiam", "eu", "euismod", "facilisis", "fames", "faucibus", 33 | "felis", "fermentum", "feugiat", "fringilla", "fusce", "gravida", "habitant", "habitasse", "hac", 34 | "hendrerit", "himenaeos", "iaculis", "id", "imperdiet", "in", "inceptos", "integer", "interdum", 35 | "ipsum", "justo", "lacinia", "lacus", "laoreet", "lectus", "leo", "libero", "ligula", "litora", 36 | "lobortis", "lorem", "luctus", "maecenas", "magna", "malesuada", "massa", "mattis", "mauris", "metus", 37 | "mi", "molestie", "mollis", "morbi", "nam", "nec", "neque", "netus", "nibh", "nisi", "nisl", "non", 38 | "nostra", "nulla", "nullam", "nunc", "odio", "orci", "ornare", "pellentesque", "per", "pharetra", 39 | "phasellus", "placerat", "platea", "porta", "porttitor", "posuere", "potenti", "praesent", "pretium", 40 | "primis", "proin", "pulvinar", "purus", "quam", "quis", "quisque", "quisque", "rhoncus", "risus", 41 | "rutrum", "sagittis", "sapien", "scelerisque", "sed", "sem", "semper", "senectus", "sit", "sociosqu", 42 | "sodales", "sollicitudin", "suscipit", "suspendisse", "taciti", "tellus", "tempor", "tempus", 43 | "tincidunt", "torquent", "tortor", "tristique", "turpis", "ullamcorper", "ultrices", "ultricies", 44 | "urna", "ut", "ut", "varius", "vehicula", "vel", "velit", "venenatis", "vestibulum", "vitae", 45 | "vivamus", "viverra", "volutpat", "vulputate" ], 46 | 47 | /** 48 | * Get random number 49 | * 50 | * @param {Number} x 51 | * @param {Number} y 52 | * @return {Number} 53 | */ 54 | _random: function (x, y) { 55 | var rnd = Math.random() * 2 - 1 + (Math.random() * 2 - 1) + (Math.random() * 2 - 1); 56 | return Math.round(Math.abs(rnd) * x + y); 57 | }, 58 | 59 | /** 60 | * Get random number between min and max 61 | * 62 | * @param {Number} min (optional) lower result limit 63 | * @param {Number} max (optional) upper result limit 64 | * @return {Number} random number 65 | */ 66 | _count: function (min, max) { 67 | var result; 68 | if (min && max) result = Math.floor(Math.random() * (max - min + 1) + min); 69 | else if (min) result = min; 70 | else if (max) result = max; 71 | else result = this._random(8, 2); 72 | 73 | return result; 74 | }, 75 | 76 | /** 77 | * Get random words 78 | * 79 | * @param {Number} min (optional) minimal words count 80 | * @param {Number} max (optional) maximal words count 81 | * @return {Object} array of random words 82 | */ 83 | words: function (min, max) { 84 | var result = []; 85 | var count = this._count(min, max); 86 | 87 | // get random words 88 | while (result.length < count) { 89 | var pos = Math.floor(Math.random() * this._words.length); 90 | var rnd = this._words[pos]; 91 | 92 | // do not allow same word twice in a row 93 | if (result.length && result[result.length - 1] === rnd) { 94 | continue; 95 | } 96 | 97 | result.push(rnd); 98 | } 99 | 100 | return result; 101 | }, 102 | 103 | /** 104 | * Generate sentence 105 | * 106 | * @param {Number} min (optional) minimal words count 107 | * @param {Number} max (optional) maximal words count 108 | * @return {String} sentence 109 | */ 110 | sentence: function (min, max) { 111 | var words = this.words(min, max); 112 | 113 | // add comma(s) to sentence 114 | var index = this._random(6, 2); 115 | while (index < words.length - 2) { 116 | words[index] += ","; 117 | index += this._random(6, 2); 118 | } 119 | 120 | // append puctation on end 121 | var punct = "...!?"; 122 | words[words.length - 1] += punct.charAt(Math.floor(Math.random() * punct.length)); 123 | 124 | // uppercase first letter 125 | words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1); 126 | 127 | return words.join(" "); 128 | }, 129 | 130 | /** 131 | * Generate paragraph 132 | * 133 | * @param {Number} min (optional) minimal words count 134 | * @param {Number} max (optional) maximal words count 135 | * @return {String} paragraph 136 | */ 137 | paragraph: function (min, max) { 138 | if (!min && !max) { 139 | min = 20; 140 | max = 60; 141 | } 142 | 143 | var result = ""; 144 | var count = this._count(min, max); 145 | 146 | // append sentences until limit is reached 147 | while (result.slice(0, -1).split(" ").length < count) { 148 | result += this.sentence() + " "; 149 | } 150 | result = result.slice(0, -1); 151 | 152 | // remove words 153 | if (result.split(" ").length > count) { 154 | var punct = result.slice(-1); 155 | result = result.split(" ").slice(0, count).join(" "); 156 | result = result.replace(/,$/, ""); 157 | result += punct; 158 | } 159 | 160 | return result; 161 | }, 162 | }; 163 | })(); 164 | 165 | // References 166 | const paragraphCount = document.getElementById("paragraph-count"); 167 | const paragraphContainer = document.getElementById("paragraph-container"); 168 | const standardIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt\ 169 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi u\ 170 | aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore\ 171 | eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt\ 172 | mollit anim id est laborum."; 173 | 174 | // 175 | // Generate the Lorem Ipsum text. 176 | function generateIpsum() { 177 | var ipsum = new LoremIpsum(); 178 | var triggered; 179 | 180 | // Get the paragraph entered, and style the element ready for appending the text. 181 | count = paragraphCount.value; 182 | paragraphContainer.innerHTML = ""; 183 | // paragraphContainer.style = "border: 3px solid #555;"; 184 | triggered = true; 185 | 186 | // For each no. of paragraphs specified, create a new 'p' element and append the Lorem Ipsum text. 187 | // The 'if (triggered)' statement will make sure the first paragraph is always the standard lorem ipsum paragraph. 188 | for (var i = 0; i < count - 1; i++) { 189 | if (triggered) { 190 | var p = document.createElement("p"); 191 | p.innerText = standardIpsum; 192 | paragraphContainer.appendChild(p); 193 | triggered = false; 194 | } 195 | var p = document.createElement("p"); 196 | p.innerText = ipsum.paragraph(Math.floor(Math.random() * 130) + 50); 197 | paragraphContainer.appendChild(p); 198 | } 199 | 200 | // If only one paragraph was specified, append the standard lorem ipsum - (The for loop doesn't work for 1 paragraph.) 201 | if (count == 1) { 202 | var p = document.createElement("p"); 203 | p.innerText = standardIpsum; 204 | paragraphContainer.appendChild(p); 205 | triggered = false; 206 | } 207 | 208 | // If the count specified is 0 or less, append a paragraph saying that nothing was generated. 209 | if (count < 1) { 210 | paragraphContainer.innerHTML = "

No text generated. Press the 'Generate' button to generate some Lorem\ 211 | Ipsum text.

"; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /public/text/word-counter.js: -------------------------------------------------------------------------------- 1 | // wordCounter.js 2 | 3 | // References 4 | const counterInput = document.getElementById("counter-input"); 5 | const wordCount = document.getElementsByClassName("word-count"); 6 | const wordCountSmall = document.getElementById("word-count-small"); 7 | const charCount = document.getElementsByClassName("char-count"); 8 | const charCountSmall = document.getElementById("char-count-small"); 9 | const sentanceCountObj = document.getElementById("sentance-count"); 10 | const paragraphCountObj = document.getElementById("paragraph-count"); 11 | const readingTimeObj = document.getElementById("reading-time"); 12 | const speakingTimeObj = document.getElementById("speaking-time"); 13 | 14 | // Variables to store the reading and speaking time. 15 | var readingTimeSeconds; 16 | var speakingTimeSeconds; 17 | 18 | // When a key is pressed in the word counter input, run the relevent functions to process the new info. 19 | counterInput.addEventListener("keyup", function (e) { 20 | wordCounter(e.target.value); 21 | charCountUpdate(e.target.value); 22 | readingTime(); 23 | speakingTime(); 24 | }); 25 | 26 | // Get word count of the input. 27 | function wordCounter(text) { 28 | var text = counterInput.value.split(" "); 29 | var count = 0; 30 | 31 | // For every individual word/string in the text area, increase the word count. 32 | for (var i = 0; i < text.length; i++) { 33 | if (text[i] !== " " && isWord(text[i])) { 34 | count++; 35 | } 36 | } 37 | 38 | // Display the correct grammer for no. of words. 39 | for (var i = 0; i < wordCount.length; i++) { 40 | if (count > 1 || count == 0) { 41 | wordCount[i].innerText = count + " words"; 42 | } else { 43 | wordCount[i].innerText = count + " word"; 44 | } 45 | } 46 | // Set the small word count for the details box 47 | wordCountSmall.innerText = count; 48 | 49 | // Set the sentance and paragraph count 50 | sentanceCountObj.innerText = counterInput.value.split(/[.?!]\s/).length; 51 | paragraphCountObj.innerText = counterInput.value.replace(/\n$/gm, "").split(/\n/).length; 52 | 53 | // Reading time on average is 4.6 words per second, Speaking time is 3 words per second. 54 | readingTimeSeconds = count / 4.6; 55 | speakingTimeSeconds = count / 3; 56 | } 57 | 58 | // Check if text entered is actually a word. 59 | function isWord(str) { 60 | var alphaNumericFound = false; 61 | for (var i = 0; i < str.length; i++) { 62 | var code = str.charCodeAt(i); 63 | // prettier-ignore 64 | if ((code > 47 && code < 58) || // Numeric (0-9) 65 | (code > 64 && code < 91) || // Upper alpha (A-Z) 66 | (code > 96 && code < 123)) { // Lower alpha (a-z) 67 | 68 | alphaNumericFound = true; 69 | return alphaNumericFound; 70 | } 71 | } 72 | return alphaNumericFound; 73 | } 74 | 75 | // Get the character count of the word counter input box. 76 | function charCountUpdate(str) { 77 | var count = str.length; 78 | 79 | // Display the correct grammer for no. of characters. 80 | for (var i = 0; i < wordCount.length; i++) { 81 | if (count > 1 || count == 0) { 82 | charCount[i].innerText = count + " characters"; 83 | } else { 84 | charCount[i].innerText = count + " character"; 85 | } 86 | } 87 | charCountSmall.innerText = count; 88 | } 89 | 90 | // Get the time taken to read text by dividing the words by the number of words readable per minute. 91 | function readingTime() { 92 | // 93 | // Handle adding the seconds, minutes and hours prefixes depending on how long the time is. 94 | if (readingTimeSeconds < 60) { 95 | readingTimeObj.innerText = Math.round(readingTimeSeconds) + " sec"; 96 | } else if (readingTimeSeconds > 60) { 97 | if (readingTimeSeconds > 3600) { 98 | var hours = Math.floor(readingTimeSeconds / 3600); 99 | var minutes = Math.round((readingTimeSeconds - hours * 3600) / 60); 100 | 101 | readingTimeObj.innerText = hours + " hrs " + minutes + " mins"; 102 | } else { 103 | var minutes = Math.floor(readingTimeSeconds / 60); 104 | var seconds = Math.round(readingTimeSeconds - minutes * 60); 105 | 106 | readingTimeObj.innerText = minutes + " mins " + seconds + " sec"; 107 | } 108 | } 109 | } 110 | 111 | // Get the time taken to speak text by dividing the words by the number of words speakable per minute. 112 | function speakingTime() { 113 | // 114 | // Handle adding the seconds, minutes and hours prefixes depending on how long the time is. 115 | if (speakingTimeSeconds < 60) { 116 | speakingTimeObj.innerText = Math.round(speakingTimeSeconds) + " sec"; 117 | } else if (speakingTimeSeconds > 60) { 118 | if (speakingTimeSeconds > 3600) { 119 | var hours = Math.floor(speakingTimeSeconds / 3600); 120 | var minutes = Math.round((speakingTimeSeconds - hours * 3600) / 60); 121 | 122 | speakingTimeObj.innerText = hours + " hrs " + minutes + " mins"; 123 | } else { 124 | var minutes = Math.floor(speakingTimeSeconds / 60); 125 | var seconds = Math.round(speakingTimeSeconds - minutes * 60); 126 | 127 | speakingTimeObj.innerText = minutes + " mins " + seconds + " sec"; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | # Group 1 2 | User-agent: * 3 | Disallow: 4 | 5 | Sitemap: https://webdevhub.herokuapp.com/sitemap.xml -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // server.js 2 | if (process.env.NODE_ENV != "production") { 3 | require("dotenv").config(); 4 | } 5 | 6 | // Main dependencies 7 | const express = require("express"); 8 | const app = express(); 9 | const server = require("http").Server(app); 10 | const convert = require("xml-js"); 11 | const colourConvert = require("color-convert"); 12 | 13 | // Handle 'sitemap.xml' and 'robots.txt' functionality so crawl bots can access them. 14 | app.get("/robots.txt", function (req, res) { 15 | res.sendFile(__dirname + "/robots.txt"); 16 | }); 17 | app.get("/sitemap.xml", function (req, res) { 18 | res.sendFile(__dirname + "/sitemap.xml"); 19 | }); 20 | 21 | // If the user has a trailing '/' at the end of a URL, remove it and refresh. 22 | app.use((req, res, next) => { 23 | if (req.path.charAt(req.path.length - 1) === "/" && req.path.length > 1) { 24 | const query = req.url.slice(req.path.length); 25 | const safepath = req.path.slice(0, -1).replace(/\/+/g, "/"); 26 | res.redirect(301, safepath + query); 27 | } else { 28 | next(); 29 | } 30 | }); 31 | 32 | // Declare ejs, JSON formatting and set static files folder. 33 | app.set("view engine", "ejs"); 34 | app.set("json spaces", 2); 35 | app.use(express.static("public")); 36 | 37 | app.use(express.urlencoded({ extended: true })); 38 | app.use(express.json()); 39 | 40 | // Home 41 | app.get("/", (req, res) => { 42 | // Render the page with given paramaters. 43 | res.render("index", { 44 | title: "Home", 45 | }); 46 | }); 47 | app.get("/code-formatter", (req, res) => { 48 | // Render the page with given paramaters. 49 | res.render("formatters/code-formatter", { 50 | title: "Code Formatter", 51 | }); 52 | }); 53 | app.get("/hex-to-filter", (req, res) => { 54 | // Render the page with given paramaters. 55 | res.render("converters/hex-to-filter", { 56 | title: "Hex to Filter", 57 | }); 58 | }); 59 | app.get("/image-converter", (req, res) => { 60 | // Render the page with given paramaters. 61 | res.render("converters/image-converter", { 62 | title: "Image Converter", 63 | }); 64 | }); 65 | app.get("/lorem-ipsum-generator", (req, res) => { 66 | // Render the page with given paramaters. 67 | res.render("text/lorem-ipsum-generator", { 68 | title: "Lorem Ipsum Generator", 69 | }); 70 | }); 71 | app.get("/word-counter", (req, res) => { 72 | // Render the page with given paramaters. 73 | res.render("text/word-counter", { 74 | title: "Word Counter", 75 | }); 76 | }); 77 | app.get("/colour-converter", (req, res) => { 78 | // Render the page with given paramaters. 79 | res.render("converters/colour-converter", { 80 | title: "Colour Converter", 81 | }); 82 | }); 83 | app.get("/color-converter", (req, res) => { 84 | // Render the page with given paramaters. 85 | res.redirect("/colour-converter"); 86 | }); 87 | app.get("/json-xml-converter", (req, res) => { 88 | // Render the page with given paramaters. 89 | res.render("converters/json-xml-converter", { 90 | title: "Json to XML Converter", 91 | }); 92 | }); 93 | app.get("/epoch-time-converter", (req, res) => { 94 | // Render the page with given paramaters. 95 | res.render("converters/epoch-time-converter", { 96 | title: "Epoch to Standard Date Time Converter", 97 | }); 98 | }); 99 | 100 | /* POST Requests */ 101 | app.post("/convert-colour", (req, res) => { 102 | // Get the colour and the input value, minus any non numbers. 103 | var colour = ""; 104 | const inputValue = req.body.focusedInputValue.replace(/[^\d,-]/g, ""); 105 | 106 | // Depending on the colour type, convert it to hex to use later. 107 | if (req.body.focusedInputType == "hex") { 108 | colour = req.body.focusedInputValue.substring(1); 109 | } else if (req.body.focusedInputType == "rgb") { 110 | const splitArray = inputValue.split(","); 111 | console.log(splitArray); 112 | colour = colourConvert.rgb.hex( 113 | parseInt(splitArray[0]), 114 | parseInt(splitArray[1]), 115 | parseInt(splitArray[2]) 116 | ); 117 | } else if (req.body.focusedInputType == "hsl") { 118 | const splitArray = inputValue.split(","); 119 | colour = colourConvert.hsl.hex( 120 | parseInt(splitArray[0]), 121 | parseInt(splitArray[1]), 122 | parseInt(splitArray[2]) 123 | ); 124 | } else if (req.body.focusedInputType == "hwb") { 125 | const splitArray = inputValue.split(","); 126 | colour = colourConvert.hwb.hex( 127 | parseInt(splitArray[0]), 128 | parseInt(splitArray[1]), 129 | parseInt(splitArray[2]) 130 | ); 131 | } 132 | 133 | // Put all of the converted colours in an object. 134 | const colourObj = {}; 135 | colourObj.hex = colour; 136 | colourObj.rgb = colourConvert.hex.rgb(colour); 137 | colourObj.hsl = colourConvert.hex.hsl(colour); 138 | colourObj.hwb = colourConvert.hex.hwb(colour); 139 | 140 | // Send the converted colours back to the client. 141 | res.status(200).json({ colourObj: colourObj }); 142 | }); 143 | app.post("/convert-json-data", (req, res) => { 144 | // Get the conversion type and code, while initialisng the result 145 | const conversionType = req.body.conversionType; 146 | const toConvertCode = req.body.toConvertCode; 147 | var result = ""; 148 | 149 | // Remove the "_text" key that the 'xml2json' function creates 150 | var removeJsonTextAttribute = function (value, parentElement) { 151 | try { 152 | var keyNo = Object.keys(parentElement._parent).length; 153 | var keyName = Object.keys(parentElement._parent)[keyNo - 1]; 154 | parentElement._parent[keyName] = nativeType(value); 155 | } catch (e) {} 156 | }; 157 | 158 | // Check for native type. 159 | function nativeType(value) { 160 | var nValue = Number(value); 161 | if (!isNaN(nValue)) { 162 | return nValue; 163 | } 164 | var bValue = value.toLowerCase(); 165 | if (bValue === "true") { 166 | return true; 167 | } else if (bValue === "false") { 168 | return false; 169 | } 170 | return value; 171 | } 172 | 173 | // Check for the conversion type and convert the correct code. 174 | if (conversionType == "javascript") { 175 | result = convert.xml2json(toConvertCode, { 176 | compact: true, 177 | ignoreComment: true, 178 | textFn: removeJsonTextAttribute, 179 | spaces: 4, 180 | }); 181 | } else if (conversionType == "xml") { 182 | result = convert.json2xml(toConvertCode, { 183 | compact: true, 184 | ignoreComment: true, 185 | spaces: 4, 186 | }); 187 | } 188 | 189 | // Send the converted code back to the client. 190 | res.status(200).json({ result: result }); 191 | }); 192 | 193 | // Initialise the server on port 3000. 194 | const PORT = process.env.PORT || 3000; 195 | server.listen(PORT); 196 | -------------------------------------------------------------------------------- /sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | https://webdevhub.herokuapp.com/ 12 | 2022-07-05T07:25:11+00:00 13 | 1.00 14 | 15 | 16 | https://webdevhub.herokuapp.com/code-formatter 17 | 2022-07-05T07:25:11+00:00 18 | 0.80 19 | 20 | 21 | https://webdevhub.herokuapp.com/image-converter 22 | 2022-07-05T07:25:11+00:00 23 | 0.80 24 | 25 | 26 | https://webdevhub.herokuapp.com/word-counter 27 | 2022-07-05T07:25:11+00:00 28 | 0.80 29 | 30 | 31 | https://webdevhub.herokuapp.com/hex-to-filter 32 | 2022-07-05T07:25:11+00:00 33 | 0.80 34 | 35 | 36 | https://webdevhub.herokuapp.com/json-xml-converter 37 | 2022-07-16T07:25:11+00:00 38 | 0.80 39 | 40 | 41 | https://webdevhub.herokuapp.com/colour-converter 42 | 2022-07-16T07:25:11+00:00 43 | 0.80 44 | 45 | 46 | -------------------------------------------------------------------------------- /views/converters/colour-converter.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include("../header.ejs") %> 8 | 9 | 10 | 11 |
12 |
13 |
14 |

Colour Converter

15 | Convert different colour types such as HEX and RGB into other formats. 16 |
17 |
18 | 19 |
20 |
21 |
  • 22 | 23 | 24 | 28 |
  • 29 |
  • 30 | 31 | 32 | 36 |
  • 37 |
  • 38 | 39 | 40 | 44 |
  • 45 |
  • 46 | 47 | 48 | 52 |
  • 53 |
    54 |
    55 |

    Result:

    56 |
    57 |
    58 |
    59 |
    60 |
    61 |
    62 |

    Also check out the Hex to CSS filter: converter 63 | here.

    64 |
    65 |
    66 | 67 |
    68 |
    69 | 70 | 71 | <%- include("../footer.ejs") %> 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /views/converters/epoch-time-converter.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | <%- include("../header.ejs") %> 11 | 12 | 13 | 14 |
    15 |
    16 |
    17 |

    Epoch to Date Converter

    18 | Convert Epoch Time to Readable Data Time. 19 |
    20 |
    21 | 22 | 23 |
    24 |
    25 |
    26 |
    27 | Epoch Time 28 |
    29 | 35 |
    36 |
    37 | Standard Date Time 38 |
    39 | 40 | 43 | Copied! 44 |
    45 |
    46 | 47 | 48 |
    49 | 50 |
    51 | 52 | 53 |
    54 |
    55 | Feature added by 57 | @PranavThampi 59 |
    60 |
    61 |
    62 | 63 | 64 | <%- include("../footer.ejs") %> 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /views/converters/hex-to-filter.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include("../header.ejs") %> 8 | 9 | 10 | 11 |
    12 |
    13 |
    14 |

    Hex to CSS filter:

    15 | Convert a Hex color into a CSS filter: component to recolor images without having to make a new one. 16 |
    17 |
    18 | 19 |
    20 |
    21 | 22 | 23 | 24 | 25 |

    Real pixel, color applied through CSS background-color:

    26 |
    27 | 28 |

    Filtered pixel, color applied through CSS filter:

    29 |
    30 | 31 |



    32 | filter: 33 |

    34 |
    35 |
    36 |

    37 | For this code to work well, the starting color of your icon needs to be black. If your icon set isn't black you can prepend brightness(0) saturate(100%) to your filter property which will first turn the icon set to black. 38 | See the example below 39 |

    40 |
    41 |                                 
    42 | .nav-icons img {  // Your image/icon css
    43 |     width: 25px;
    44 |     height: 25px;
    45 |     filter: brightness(0) saturate(100%);           // Prepended filter
    46 |     filter: invert(40%) sepia(89%) contrast(109%);  // Filter
    47 | }
    48 |                                 
    49 |                             
    50 |
    51 |
    52 |
    53 |
    54 | 55 | 56 | <%- include("../footer.ejs") %> 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /views/converters/image-converter.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include("../header.ejs") %> 8 | 9 | 10 | 11 |
    12 |
    13 | 14 | 15 |
    16 |

    Image Converter

    17 | Convert an image to jpg, png and other formats. 18 |

    Disclaimer: This feature is currently unavailable due to the lack of 19 | server-side integration on the site. Server-side code will be implemented in the future 20 | as the site scales.

    21 |
    22 |
    23 | Upload an image... 24 |
    25 |
    26 | 27 | 28 |
    29 | 30 | 31 | 32 | 33 | 34 |
    35 | 36 | Preview Image 37 |
    38 | 39 | 40 |
    41 | 42 | 43 | 44 | 51 | 52 | 53 | 54 |
    55 | 56 | 57 |
    58 | 59 | 60 |
    61 | 62 | 63 |
    64 | 65 | 66 |
    67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
    75 | Feature added by @foobar41 76 |
    77 |
    78 |
    79 | 80 | 81 | <%- include("../footer.ejs") %> 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /views/converters/json-xml-converter.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include("../header.ejs") %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 |
    19 |
    20 |

    JSON to XML Converter

    21 | Convert JSON data to XML format and vise-versa. 22 |
    23 |
    24 | 25 | 26 |
    27 |
    28 |
    29 |
    30 | JSON 31 |
    32 | 33 |
    34 |
    35 | XML 36 |
    37 | 38 | 41 | Copied! 42 |
    43 |
    44 | 45 |
    46 | Something went wrong with the conversion. Please check your code for errors, typos 47 | etc. then try again. 48 |
    49 | 50 | 51 |
    52 | 53 | 57 | 58 |
    59 | 60 | 61 |
    62 |
    63 | Feature added by @thhamiltonsmith 64 |
    65 |
    66 |
    67 | 68 | 69 | <%- include("../footer.ejs") %> 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /views/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /views/formatters/code-formatter.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include("../header.ejs") %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 |
    19 |
    20 |

    Code Formatter

    21 | Format code with specified spacing, indentation etc. 22 |
    23 |
    24 | 25 | 26 |
    27 |
    28 |
    29 |
    30 | Code Editor 31 |
    32 | 33 |
    34 |
    35 | Formatted Code 36 |
    37 | 38 | 41 | Copied! 42 |
    43 |
    44 | 45 | 46 |
    47 | 48 | 53 | 54 |
    55 | 56 | 57 |
    58 |
    59 | Feature added by @thhamiltonsmith 60 |
    61 |
    62 |
    63 | 64 | 65 | <%- include("../footer.ejs") %> 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /views/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= title %> - WebDevHub 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 38 | 39 | 40 | 41 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebDevHub 6 | 7 | 8 | 9 | <%- include("header.ejs") %> 10 | 11 | 12 | 13 |
    14 |
    15 |

    WebDevHub

    16 | A hub of tools for developers to help speed up development. 17 |
    18 |

    Disclaimer: WebDevHub is still in early development, so not 19 | all of our planned features are currently available. Working features are highlighted in blue. The 20 | site is open source and relies on, contributers to help develop the app and add new features. If you 21 | would like to check out the code or contribute to the site, view the GitHub page 22 | here. If you would like to suggest a 23 | feature, create an issue here. 24 |

    25 | 26 |

    Popular Features

    27 |
    28 | 29 |
    30 | 31 | 32 | 35 | HTML Formatter 36 | 37 | 38 | 41 | CSS Formatter 42 | 43 | 44 | 47 | JS Formatter 48 | 49 | 50 | 53 | Image Converter 54 | 55 | 56 | 59 | Word Counter 60 | 61 | 62 | 65 | Colour Converter 66 | 67 | 68 | 71 | Hex to CSS filter: 72 | 73 | 74 | 77 | Epoch Converter 78 | 79 | 80 | 83 | JSON to XML Converter 84 | 85 | 86 | 89 | Lorem Ipsum Generator 90 | 91 |
    92 | 93 |
    94 |

    Code Formatters

    95 | Format code into a more readable format by fixing indentation, spacing etc. 96 |
    97 | 122 |
    123 |
    124 | 125 |
    126 |

    Converters

    127 | Convert JSON to XML, colour types, file types and more. 128 |
    129 | 161 |
    162 |
    163 | 164 |
    165 |

    Paragraphs and Text

    166 | Get the word count of a body of text, generate dummy 'Lorem Ipsum' text and more. 167 |
    168 | 182 |
    183 |
    184 | 185 |
    186 |

    Other Features

    187 | Some other features the site offers that don't yet have a category. 188 |
    189 | 197 |
    198 |
    199 |
    200 | 201 | 202 | <%- include("footer.ejs") %> 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /views/text/lorem-ipsum-generator.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include("../header.ejs") %> 8 | 9 | 10 | 11 |
    12 |
    13 |
    14 |

    Lorem Ipsum Generator

    15 | Generate dummy text for your website. 16 |
    17 |
    18 |
    19 | Paragraphs: 20 | 21 | 22 |
    23 |
    24 | 25 |

    No text generated. Press the 'Generate' button to generate some Lorem Ipsum text.

    26 |
    27 |
    28 |
    29 | 30 | 31 | <%- include("../footer.ejs") %> 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /views/text/word-counter.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include("../header.ejs") %> 8 | 9 | 10 | 11 |
    12 |
    13 |
    14 |

    Word Counter

    15 | Count the words, characters, sentances and paragraphs of a text. 16 |
    17 |
    18 |
    19 |
    20 |
    21 |

    0 words 0 characters

    22 |
    23 | 24 | 27 |
    28 |
    29 |
      30 |
    • Details
    • 31 |
    • Words0
    • 32 |
    • Characters0
    • 33 |
    • Sentences0
    • 34 |
    • Paragraphs0
    • 35 | 36 |
    • Reading Time0 sec
    • 37 |
    • Speaking Time0 sec
    • 38 |
    39 |
    40 |
    41 |
    42 |
    43 | 44 | 45 | <%- include("../footer.ejs") %> 46 | 47 | 48 | 49 | 50 | 51 | 52 | --------------------------------------------------------------------------------