├── .env.example
├── .gitattributes
├── .github
├── dependabot.yml
└── workflows
│ ├── BadgesList.yml
│ ├── ThemeList.yml
│ └── monkeytype-readme.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── README.md
├── action.yml
├── app.js
├── monkeytype-data
├── badges.json
└── themes.json
├── package-lock.json
├── package.json
├── public
├── assets
│ ├── robots.txt
│ └── sitemap.xml
├── image
│ ├── apeMonkeyIcon.png
│ ├── github-30*30.svg
│ ├── github.png
│ ├── github.svg
│ ├── github.webp
│ ├── plus-solid.svg
│ ├── stupidMonkeyIcon-30*30.svg
│ ├── stupidMonkeyIcon.png
│ ├── stupidMonkeyIcon.svg
│ ├── stupidMonkeyIcon.webp
│ ├── stupidMonkeyIconWhite.png
│ ├── stupidMonkeyIconWhite.webp
│ └── userImg
│ │ └── README.md
├── script
│ ├── generateSvg.js
│ ├── index.js
│ ├── monkeytypeData.js
│ ├── prism.js
│ ├── tailwindCSS.js
│ └── user.js
├── style
│ ├── input.css
│ ├── output.css
│ └── prism.css
└── views
│ ├── favicon.ejs
│ ├── index.ejs
│ ├── logo.ejs
│ └── user.ejs
└── tailwind.config.js
/.env.example:
--------------------------------------------------------------------------------
1 | DOMAIN=your-domain
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.css linguist-detectable=false
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 | labels:
9 | - "dependencies"
10 |
11 | # Maintain dependencies for yarn
12 | - package-ecosystem: "npm"
13 | directory: "/"
14 | schedule:
15 | interval: "daily"
16 | labels:
17 | - "dependencies"
18 |
--------------------------------------------------------------------------------
/.github/workflows/BadgesList.yml:
--------------------------------------------------------------------------------
1 | name: Update monkeytype badges json list
2 |
3 | on:
4 | schedule:
5 | - cron: "0 */24 * * *"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | make-request:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 |
15 | - name: Set up Node.js
16 | uses: actions/setup-node@v4
17 | with:
18 | node-version: "20.x"
19 |
20 | - name: Get badge json data from monkeytype
21 | run: curl -o monkeytype-data/badges.json https://monkeytype-readme.zeabur.app/mr-command/badge
22 |
23 | - name: Format code with Prettier
24 | run: npx prettier --write ./monkeytype-data/badges.json
25 |
26 | - name: Print json file contents
27 | run: cat monkeytype-data/badges.json
28 |
29 | - name: Update badge list
30 | uses: actions/github-script@v7
31 | with:
32 | script: |
33 | const fs = require('fs');
34 | let monkeytypeBadgesJson = fs.readFileSync('monkeytype-data/badges.json');
35 | const filePath = '${{ github.workspace }}/monkeytype-data/badges.json';
36 | fs.writeFileSync(filePath, monkeytypeBadgesJson);
37 |
38 | - name: Configure git
39 | run: |
40 | git config --global user.name "${{ vars.GITUSERNAME }}"
41 | git config --global user.email "${{ vars.GITUSEREMAIL }}"
42 |
43 | - name: Commit and push changes
44 | run: |
45 | if git diff-index --quiet HEAD --; then
46 | echo "No changes detected."
47 | else
48 | git add .
49 | git commit -m "chore: update badges json list"
50 | fi
51 | continue-on-error: true
52 |
53 | - name: Push changes
54 | uses: ad-m/github-push-action@master
55 | with:
56 | branch: master
57 | github_token: ${{ secrets.GITHUB_TOKEN }}
58 |
--------------------------------------------------------------------------------
/.github/workflows/ThemeList.yml:
--------------------------------------------------------------------------------
1 | name: Update monkeytype theme json list
2 |
3 | on:
4 | schedule:
5 | - cron: "0 */24 * * *"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | make-request:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 |
15 | - name: Set up Node.js
16 | uses: actions/setup-node@v4
17 | with:
18 | node-version: "20.x"
19 |
20 | - name: Download theme json file from monkeytype
21 | run: wget --timeout=180 --tries=3 --waitretry=10 --output-document=monkeytype-data/themes.json https://monkeytype-readme.zeabur.app/mr-command/theme
22 | timeout-minutes: 10
23 |
24 | - name: Format JSON data
25 | run: |
26 | python3 -c 'import json; data = json.load(open("monkeytype-data/themes.json")); json.dump(data, open("monkeytype-data/themes.json", "w"), indent=2)'
27 |
28 | - name: Format code with Prettier
29 | run: npx prettier --write ./monkeytype-data/themes.json
30 |
31 | - name: Print json file contents
32 | run: cat monkeytype-data/themes.json
33 |
34 | - name: Update theme list
35 | uses: actions/github-script@v7
36 | with:
37 | script: |
38 | const fs = require('fs');
39 | let monkeytypeThemesJson = fs.readFileSync('monkeytype-data/themes.json');
40 | const filePath = '${{ github.workspace }}/monkeytype-data/themes.json';
41 | fs.writeFileSync(filePath, monkeytypeThemesJson);
42 |
43 | - name: Print updated theme contents
44 | run: |
45 | cat monkeytype-data/themes.json
46 |
47 | - name: Configure git
48 | run: |
49 | git config --global user.name "${{ vars.GITUSERNAME }}"
50 | git config --global user.email "${{ vars.GITUSEREMAIL }}"
51 |
52 | - name: Commit and push changes
53 | run: |
54 | if git diff-index --quiet HEAD --; then
55 | echo "No changes detected."
56 | else
57 | git add .
58 | git commit -m "chore: update themes json list"
59 | fi
60 | continue-on-error: true
61 |
62 | - name: Push changes
63 | uses: ad-m/github-push-action@master
64 | with:
65 | branch: master
66 | github_token: ${{ secrets.GITHUB_TOKEN }}
67 |
--------------------------------------------------------------------------------
/.github/workflows/monkeytype-readme.yml:
--------------------------------------------------------------------------------
1 | name: generate monkeytype readme svg
2 |
3 | on:
4 | schedule:
5 | - cron: "0 */24 * * *"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | download-svg:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 |
15 | - name: Set up Node.js
16 | uses: actions/setup-node@v4
17 | with:
18 | node-version: "20.x"
19 |
20 | - name: Download SVG
21 | run: |
22 | mkdir monkeytype-readme-svg
23 | curl -o monkeytype-readme-svg/monkeytype-readme.svg https://monkeytype-readme.zeabur.app/generate-svg/rocket/botanical
24 | curl -o monkeytype-readme-svg/monkeytype-readme-lb.svg https://monkeytype-readme.zeabur.app/generate-svg/rocket/botanical?lb=true
25 | curl -o monkeytype-readme-svg/monkeytype-readme-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/rocket/botanical?pb=true
26 | curl -o monkeytype-readme-svg/monkeytype-readme-lb-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/rocket/botanical?lbpb=true
27 |
28 | curl -o monkeytype-readme-svg/monkeytype-readme-Miodec.svg https://monkeytype-readme.zeabur.app/generate-svg/Miodec/nord_light
29 | curl -o monkeytype-readme-svg/monkeytype-readme-Miodec-lb.svg https://monkeytype-readme.zeabur.app/generate-svg/Miodec/nord_light?lb=true
30 | curl -o monkeytype-readme-svg/monkeytype-readme-Miodec-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/Miodec/nord_light?pb=true
31 | curl -o monkeytype-readme-svg/monkeytype-readme-Miodec-lb-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/Miodec/nord_light?lbpb=true
32 |
33 | curl -o monkeytype-readme-svg/monkeytype-readme-rocket-slambook-lb-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/rocket/slambook?lbpb=true
34 |
35 | curl -o monkeytype-readme-svg/monkeytype-readme-UTF8.svg https://monkeytype-readme.zeabur.app/generate-svg/UTF8/camping
36 |
37 | curl -o monkeytype-readme-svg/monkeytype-readme-ridemountainpig.svg https://monkeytype-readme.zeabur.app/generate-svg/ridemountainpig/witch_girl
38 |
39 | curl -o monkeytype-readme-svg/monkeytype-readme-semi.svg https://monkeytype-readme.zeabur.app/generate-svg/semi/blueberry_light
40 |
41 | curl -o monkeytype-readme-svg/monkeytype-readme-mac-lb.svg https://monkeytype-readme.zeabur.app/generate-svg/mac/mizu?lb=true
42 |
43 | curl -o monkeytype-readme-svg/monkeytype-readme-ze_or.svg https://monkeytype-readme.zeabur.app/generate-svg/ze_or/darling
44 |
45 | curl -o monkeytype-readme-svg/monkeytype-readme-nask-lb.svg https://monkeytype-readme.zeabur.app/generate-svg/nask/beach?lb=true
46 |
47 | curl -o monkeytype-readme-svg/monkeytype-readme-davidho0403-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/davidho0403/lil_dragon?pb=true
48 |
49 | - name: push monkeytype-readme.svg to the monkeytype-readme branch
50 | uses: crazy-max/ghaction-github-pages@v4.0.0
51 | with:
52 | target_branch: monkeytype-readme
53 | build_dir: monkeytype-readme-svg
54 | env:
55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | public/image/userImg/*
4 | !public/image/userImg/README.md
5 | public/views/test.html
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | public/style/output.css
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # MonkeyType Readme
6 |
7 | Monkeytype Readme transforms MonkeyType typing data into SVGs for your GitHub Readme.
8 |
9 |
10 |
11 |
12 |
13 | ## Usage
14 |
15 | Two way to generate MonkeyType Readme
16 |
17 | 1. #### Using GitHub Action from Marketplace
18 | 2. #### Setting Up Custom MonkeyType Readme GitHub Action
19 |
20 | ### Using GitHub Action from Marketplace ([MonkeyType Readme Github Action](https://github.com/marketplace/actions/monkeytype-readme))
21 |
22 | 1. Add a `monkeytype-readme.yml` file in your repository's `.github/workflows/` path.
23 | 2. Configure `monkeytype-readme.yml` with the following format:
24 |
25 | - **Username**: Change `MONKEYTYPE_USERNAME` to your username in MonkeyType.
26 |
27 | - **Themes**: Change `MONKEYTYPE_THEME_NAME` to your favorite theme in MonkeyType.
28 | If theme name have `space`, please change `space` to `_`.
29 |
30 | > Example: `nord light` => `nord_light`
31 |
32 | - **Target Branch**: Change `BRANCH_NAME` to the branch you want to put MonkeyType Readme.
33 |
34 | ```yml
35 | name: generate monkeytype readme svg
36 |
37 | on:
38 | schedule:
39 | - cron: "0 */6 * * *" # every 6 hours
40 | workflow_dispatch:
41 |
42 | jobs:
43 | download-svg:
44 | runs-on: ubuntu-latest
45 |
46 | steps:
47 | - name: Checkout code
48 | uses: actions/checkout@v4
49 |
50 | - name: Set up Node.js
51 | uses: actions/setup-node@v3
52 | with:
53 | node-version: "20.x"
54 |
55 | - name: Generate Monkeytype Readme SVG
56 | uses: monkeytype-hub/monkeytype-readme@v1.0.0
57 | with:
58 | username: MONKEYTYPE_USERNAME
59 | themes: MONKEYTYPE_THEME_NAME
60 | target-branch: BRANCH_NAME
61 | github-token: ${{ secrets.GITHUB_TOKEN }}
62 | ```
63 |
64 | 3. Go to actions and run `generate monkeytype readme svg` workflow.
65 |
66 | 4. Done! Now, navigate to your target branch and you'll find the MonkeyType README file. You can also integrate it into your GitHub README.
67 |
68 | ### Setting Up Custom MonkeyType Readme GitHub Action
69 |
70 | 1. Add a `monkeytype-readme.yml` file in your repository's `.github/workflows/` path.
71 | 2. Configure `monkeytype-readme.yml` with the following format:
72 |
73 | > Note: change YOUR_USERNAME to your MonkeyType username.
74 |
75 | > Note: This workflow will auto to update your MonkeyType Readme.
76 |
77 | #### Themes
78 |
79 | Change `THEMES` to your favorite theme in MonkeyType.
80 | If theme name have `space`, please change `space` to `_`.
81 |
82 | > Example: `nord light` => `nord_light`
83 |
84 | #### SVGs information
85 |
86 |
87 |
88 |
89 |
90 | #### With LeaderBoards: `?lb=true`
91 |
92 |
93 |
94 |
95 |
96 | #### With PersonalBests: `?pb=true`
97 |
98 |
99 |
100 |
101 |
102 | #### With LeaderBoards and PersonalBests: `?lbpb=true`
103 |
104 | #### github actions
105 |
106 | ```yml
107 | name: generate monkeytype readme svg
108 |
109 | on:
110 | schedule:
111 | - cron: "0 */6 * * *" # every 6 hours
112 | workflow_dispatch:
113 |
114 | jobs:
115 | download-svg:
116 | runs-on: ubuntu-latest
117 | steps:
118 | - name: Checkout code
119 | uses: actions/checkout@v3
120 |
121 | - name: Set up Node.js
122 | uses: actions/setup-node@v3
123 | with:
124 | node-version: "20.x"
125 |
126 | - name: Download SVG
127 | run: |
128 | mkdir public
129 | curl -o public/monkeytype-readme.svg https://monkeytype-readme.zeabur.app/generate-svg/YOUR_USERNAME/THEMES
130 | curl -o public/monkeytype-readme-lb.svg https://monkeytype-readme.zeabur.app/generate-svg/YOUR_USERNAME/THEMES?lb=true
131 | curl -o public/monkeytype-readme-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/YOUR_USERNAME/THEMES?pb=true
132 | curl -o public/monkeytype-readme-lb-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/YOUR_USERNAME/THEMES?lbpb=true
133 |
134 | - name: push monkeytype-readme.svg to the monkeytype-readme branch
135 | uses: crazy-max/ghaction-github-pages@v4.0.0
136 | with:
137 | target_branch: monkeytype-readme
138 | build_dir: public
139 | env:
140 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
141 | ```
142 |
143 | 3. Add SVGs to your GitHub Readme.
144 |
145 | > Note:
146 | > change YOUR_MONKEYTYPE_USERNAME to your MonkeyType username.
147 | > change YOUR_GITHUB_USERNAME to your Github username.
148 | > change YOUR_GITHUB_REPOSITORY to your repository name.
149 | > change SVG_NAME to the svg you want to use.
150 | >
151 | > > original : monkeytype-readme.svg
152 | > > original + leader boards : monkeytype-readme-lb.svg
153 | > > original + personal bests : monkeytype-readme-pb.svg
154 | > > original + leader boards + personal bests : monkeytype-readme-lbpb.svg
155 |
156 | ```md
157 |
158 |
159 |
160 | ```
161 |
162 | 4. Go to actions and run `generate monkeytype readme svg` workflow.
163 |
164 | 5. Done! Your MonkeyType Readme will show on your Readme.
165 |
166 | ## Running Locally
167 |
168 | To run MonkeyType Readme locally, follow these steps:
169 |
170 | 1. Clone this repository:
171 |
172 | ```bash
173 | git clone https://github.com/monkeytype-hub/monkeytype-readme.git
174 | ```
175 |
176 | 2. Store the MonkeyType APE keys in `.env`:
177 |
178 | ```bash
179 | cp .env.example .env
180 | ```
181 |
182 | 3. Install the dependencies:
183 |
184 | ```bash
185 | npm install
186 | ```
187 |
188 | 4. Run the application:
189 |
190 | ```bash
191 | npm run dev
192 | ```
193 |
194 | 5. Finally, visit [http://localhost:3000](http://localhost:3000/) in your web browser.
195 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: "Monkeytype Readme"
2 | description: "A GitHub Action to generate Monkeytype Readme SVGs, let you share Monkeytype story with the world."
3 | branding:
4 | icon: "bookmark"
5 | color: "yellow"
6 | inputs:
7 | username:
8 | description: "Your Monkeytype username"
9 | required: true
10 | themes:
11 | description: "Themes to generate SVGs for"
12 | required: true
13 | target-branch:
14 | description: "Branch to deploy Monkeytype Readme SVGs"
15 | required: true
16 | github-token:
17 | description: "GitHub token use to deploy github pages"
18 | required: true
19 |
20 | runs:
21 | using: "composite"
22 | steps:
23 | - name: Checkout code
24 | uses: actions/checkout@v4
25 |
26 | - name: Set up Node.js
27 | uses: actions/setup-node@v4
28 | with:
29 | node-version: "20.x"
30 |
31 | - name: Download SVG
32 | shell: bash
33 | run: |
34 | mkdir monkeytype-readme-svg
35 | curl -o monkeytype-readme-svg/${{ inputs.username }}-monkeytype-readme.svg https://monkeytype-readme.zeabur.app/generate-svg/${{ inputs.username }}/${{ inputs.themes }}
36 | curl -o monkeytype-readme-svg/${{ inputs.username }}-monkeytype-readme-lb.svg https://monkeytype-readme.zeabur.app/generate-svg/${{ inputs.username }}/${{ inputs.themes }}?lb=true
37 | curl -o monkeytype-readme-svg/${{ inputs.username }}-monkeytype-readme-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/${{ inputs.username }}/${{ inputs.themes }}?pb=true
38 | curl -o monkeytype-readme-svg/${{ inputs.username }}-monkeytype-readme-lb-pb.svg https://monkeytype-readme.zeabur.app/generate-svg/${{ inputs.username }}/${{ inputs.themes }}?lbpb=true
39 |
40 | - name: push monkeytype-readme.svg to the monkeytype-readme branch
41 | uses: crazy-max/ghaction-github-pages@v4.0.0
42 | with:
43 | target_branch: ${{ inputs.target-branch }}
44 | build_dir: monkeytype-readme-svg
45 | env:
46 | GITHUB_TOKEN: ${{ inputs.github-token }}
47 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const compression = require("compression");
3 | const path = require("path");
4 | const axios = require("axios");
5 | const app = express();
6 |
7 | const {
8 | getTheme,
9 | getFaviconTheme,
10 | getBadge,
11 | getUserData,
12 | getMonkeyTypeThemesData,
13 | getMonkeyTypeBadgesData,
14 | } = require("./public/script/monkeytypeData");
15 | const { getOGSvg, getSvg } = require("./public/script/generateSvg");
16 | require("dotenv").config();
17 |
18 | app.use(compression());
19 | app.use(express.static("public"));
20 | app.use("/styles", express.static("dist"));
21 |
22 | app.set("view engine", "ejs");
23 | app.set("views", path.join(__dirname, "public", "views"));
24 |
25 | app.use("/node_modules", express.static(path.join(__dirname, "node_modules")));
26 |
27 | app.get("/", (req, res) => {
28 | const data = {
29 | domain: process.env.DOMAIN,
30 | };
31 |
32 | res.render("index", { data });
33 | });
34 |
35 | app.get("/mr-command/theme", async (req, res) => {
36 | try {
37 | const themesData = await getMonkeyTypeThemesData();
38 | res.set("Content-Type", "application/json");
39 | res.send(themesData);
40 | } catch (err) {
41 | console.error("Failed to fetch themes:", err);
42 | res.status(500).json({ error: "Failed to fetch themes" });
43 | }
44 | });
45 |
46 | app.get("/mr-command/badge", async (req, res) => {
47 | try {
48 | const badgesData = await getMonkeyTypeBadgesData();
49 | res.set("Content-Type", "application/json");
50 | res.send(badgesData);
51 | } catch (err) {
52 | console.error("Failed to fetch badges:", err);
53 | res.status(500).json({ error: "Failed to fetch badges" });
54 | }
55 | });
56 |
57 | app.get("/mr-command/favicon", async (req, res) => {
58 | try {
59 | const faviconData = getFaviconTheme();
60 | res.render("favicon", { faviconData });
61 | } catch (err) {
62 | console.error("Failed to render favicon:", err);
63 | res.status(500).send("Failed to render favicon");
64 | }
65 | });
66 |
67 | app.get("/mr-command/logo", async (req, res) => {
68 | try {
69 | const faviconData = getFaviconTheme();
70 | res.render("logo", { faviconData });
71 | } catch (err) {
72 | console.error("Failed to render logo:", err);
73 | res.status(500).send("Failed to render logo");
74 | }
75 | });
76 |
77 | app.get("/sitemap.xml", (req, res) => {
78 | res.sendFile(path.join(__dirname, "public/assets", "sitemap.xml"));
79 | });
80 |
81 | app.get("/robots.txt", (req, res) => {
82 | res.sendFile(path.join(__dirname, "public/assets", "robots.txt"));
83 | });
84 |
85 | app.get(
86 | ["/og-image/:userId/:themeName", "/og-image/:userId"],
87 | async (req, res) => {
88 | const userId = req.params.userId;
89 | const themeName = req.params.themeName;
90 | req.query.lbpb == "true" ? (leaderBoards = personalBests = true) : null;
91 | const userData = await getUserData(userId);
92 | const theme = getTheme(themeName);
93 | if (userData === undefined || userData.name === undefined) {
94 | const svg = await getOGSvg(null, theme, null);
95 | res.set("Content-Type", "image/svg+xml");
96 | res.send(svg);
97 | return;
98 | }
99 | let badge = null;
100 | if (userData.inventory !== null && userData.inventory !== undefined) {
101 | if (userData.inventory.badges.length !== 0) {
102 | badge = getBadge(userData.inventory.badges[0].id);
103 | for (let i = 0; i < userData.inventory.badges.length; i++) {
104 | if (userData.inventory.badges[i].selected === true) {
105 | badge = getBadge(userData.inventory.badges[i].id);
106 | break;
107 | }
108 | }
109 | }
110 | }
111 | const ogSvg = await getOGSvg(userData, theme, badge);
112 |
113 | try {
114 | const response = await axios.post(
115 | "https://mr-api.zeabur.app/og-image",
116 | { og_svg: ogSvg },
117 | { responseType: "arraybuffer" },
118 | );
119 |
120 | res.setHeader("Content-Type", "image/png");
121 | res.send(Buffer.from(response.data, "binary"));
122 | } catch (error) {
123 | console.error("Error converting SVG to PNG:", error);
124 | res.status(500).send("Error converting SVG to PNG");
125 | }
126 | },
127 | );
128 |
129 | app.get(["/:userId/:themeName", "/:userId"], async (req, res) => {
130 | const data = {
131 | domain: process.env.DOMAIN,
132 | userId: req.params.userId,
133 | theme: getTheme(
134 | req.params.themeName ? req.params.themeName : "serika_dark",
135 | ),
136 | userData: await getUserData(req.params.userId),
137 | svgUrl: `${process.env.DOMAIN}/generate-svg/${req.params.userId}/${req.params.themeName}?lbpb=true`,
138 | };
139 |
140 | res.render("user", { data });
141 | });
142 |
143 | app.get("/generate-svg/:userId/:themeName", async (req, res) => {
144 | const userId = req.params.userId;
145 | const themeName = req.params.themeName;
146 | let leaderBoards = req.query.lb == "true" ? true : false;
147 | let personalBests = req.query.pb == "true" ? true : false;
148 | req.query.lbpb == "true" ? (leaderBoards = personalBests = true) : null;
149 | const userData = await getUserData(userId);
150 | const theme = getTheme(themeName);
151 | if (userData === undefined || userData.name === undefined) {
152 | const svg = await getSvg(null, theme, null, false, false);
153 | res.set("Content-Type", "image/svg+xml");
154 | res.send(svg);
155 | return;
156 | }
157 | let badge = null;
158 | if (userData.inventory !== null && userData.inventory !== undefined) {
159 | if (userData.inventory.badges.length !== 0) {
160 | badge = getBadge(userData.inventory.badges[0].id);
161 | for (let i = 0; i < userData.inventory.badges.length; i++) {
162 | if (userData.inventory.badges[i].selected === true) {
163 | badge = getBadge(userData.inventory.badges[i].id);
164 | break;
165 | }
166 | }
167 | }
168 | }
169 | const svg = await getSvg(
170 | userData,
171 | theme,
172 | badge,
173 | leaderBoards,
174 | personalBests,
175 | );
176 | res.set("Content-Type", "image/svg+xml");
177 | res.send(svg);
178 | });
179 |
180 | app.listen(process.env.PORT || 3000, async () => {
181 | console.log("Server started on port " + (process.env.PORT || 3000));
182 | });
183 |
--------------------------------------------------------------------------------
/monkeytype-data/badges.json:
--------------------------------------------------------------------------------
1 | {
2 | "1": {
3 | "id": 1,
4 | "name": "Developer",
5 | "description": "I made this",
6 | "icon": "fa-laptop",
7 | "color": "white",
8 | "customStyle": "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);",
9 | "iconSvg": ""
10 | },
11 | "2": {
12 | "id": 2,
13 | "name": "Collaborator",
14 | "description": "I helped make this",
15 | "icon": "fa-code",
16 | "color": "white",
17 | "customStyle": "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);",
18 | "iconSvg": ""
19 | },
20 | "3": {
21 | "id": 3,
22 | "name": "Server Mod",
23 | "description": "Discord server moderator",
24 | "icon": "fa-hammer",
25 | "color": "white",
26 | "customStyle": "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);",
27 | "iconSvg": ""
28 | },
29 | "4": {
30 | "id": 4,
31 | "name": "OG Account",
32 | "description": "First 1000 users on the site",
33 | "icon": "fa-baby",
34 | "color": "bgColor",
35 | "background": "mainColor",
36 | "iconSvg": ""
37 | },
38 | "5": {
39 | "id": 5,
40 | "name": "OG Discordian",
41 | "description": "First 1000 Discord server members",
42 | "icon": "fa-baby",
43 | "color": "bgColor",
44 | "background": "mainColor",
45 | "iconSvg": ""
46 | },
47 | "6": {
48 | "id": 6,
49 | "name": "Supporter",
50 | "description": "Donated money",
51 | "icon": "fa-heart",
52 | "color": "textColor",
53 | "background": "subColor",
54 | "iconSvg": ""
55 | },
56 | "7": {
57 | "id": 7,
58 | "name": "Sugar Daddy",
59 | "description": "Donated a lot of money",
60 | "icon": "fa-gem",
61 | "color": "bgColor",
62 | "background": "mainColor",
63 | "iconSvg": ""
64 | },
65 | "8": {
66 | "id": 8,
67 | "name": "Monkey Supporter",
68 | "description": "Donated more money",
69 | "icon": "fa-heart",
70 | "color": "bgColor",
71 | "background": "mainColor",
72 | "iconSvg": ""
73 | },
74 | "9": {
75 | "id": 9,
76 | "name": "White Hat",
77 | "description": "Reported critical vulnerabilities on the site",
78 | "icon": "fa-user-secret",
79 | "color": "bgColor",
80 | "background": "mainColor",
81 | "iconSvg": ""
82 | },
83 | "10": {
84 | "id": 10,
85 | "name": "Bug Hunter",
86 | "description": "Reported or helped track down bugs on the site",
87 | "icon": "fa-bug",
88 | "color": "textColor",
89 | "background": "subColor",
90 | "iconSvg": ""
91 | },
92 | "11": {
93 | "id": 11,
94 | "name": "Content Creator",
95 | "description": "Verified content creator",
96 | "icon": "fa-video",
97 | "color": "textColor",
98 | "background": "subColor",
99 | "iconSvg": ""
100 | },
101 | "12": {
102 | "id": 12,
103 | "name": "Contributor",
104 | "description": "Contributed to the site",
105 | "icon": "fa-hands-helping",
106 | "color": "textColor",
107 | "background": "subColor",
108 | "iconSvg": ""
109 | },
110 | "13": {
111 | "id": 13,
112 | "name": "Mythical",
113 | "description": "Yes, I'm actually this fast",
114 | "icon": "fa-rocket",
115 | "color": "white",
116 | "customStyle": "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);",
117 | "iconSvg": ""
118 | },
119 | "14": {
120 | "id": 14,
121 | "name": "All Year Long",
122 | "description": "Reached a streak of 365 days",
123 | "icon": "fa-fire",
124 | "color": "bgColor",
125 | "background": "mainColor",
126 | "iconSvg": ""
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monkeytype-readme",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node app.js",
9 | "dev": "nodemon app.js",
10 | "format": "prettier --write .",
11 | "tailwind": "tailwindcss -i ./public/style/input.css -o ./public/style/output.css --watch"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@fortawesome/fontawesome-svg-core": "^6.4.0",
17 | "@fortawesome/free-brands-svg-icons": "^6.4.0",
18 | "@fortawesome/free-regular-svg-icons": "^6.4.0",
19 | "@fortawesome/free-solid-svg-icons": "^6.4.0",
20 | "axios": "^1.6.8",
21 | "bootstrap-icons": "^1.11.1",
22 | "compression": "^1.8.0",
23 | "dotenv": "^16.5.0",
24 | "ejs": "^3.1.9",
25 | "express": "^4.18.2",
26 | "node-fetch": "^3.3.2",
27 | "request": "^2.88.2",
28 | "tailwindcss-animated": "^1.0.1"
29 | },
30 | "devDependencies": {
31 | "nodemon": "^2.0.22",
32 | "prettier": "^3.0.3",
33 | "prettier-plugin-tailwindcss": "^0.2.7",
34 | "tailwindcss": "^3.3.5"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/public/assets/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 | Disallow: /generate-svg/YOUR_USERNAME/THEMES
4 | Sitemap: https://monkeytype-readme.zeabur.app/sitemap.xml
5 |
--------------------------------------------------------------------------------
/public/assets/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 | https://monkeytype-readme.com/
11 | 2023-11-30T09:31:29+00:00
12 | 1.00
13 |
14 |
15 | https://monkeytype-readme.com/miodec/nord_light
16 | 2023-11-30T09:31:29+00:00
17 | 0.80
18 |
19 |
20 | https://monkeytype-readme.com/rocket/slambook
21 | 2023-11-30T09:31:29+00:00
22 | 0.80
23 |
24 |
25 | https://monkeytype-readme.com/UTF8/camping
26 | 2023-11-30T09:31:29+00:00
27 | 0.80
28 |
29 |
30 | https://monkeytype-readme.com/ridemountainpig/witch_girl
31 | 2023-11-30T09:31:29+00:00
32 | 0.80
33 |
34 |
35 | https://monkeytype-readme.com/semi/blueberry_light
36 | 2023-11-30T09:31:29+00:00
37 | 0.80
38 |
39 |
40 | https://monkeytype-readme.com/mac/mizu
41 | 2023-11-30T09:31:29+00:00
42 | 0.80
43 |
44 |
45 | https://monkeytype-readme.com/ze_or/darling
46 | 2023-11-30T09:31:29+00:00
47 | 0.80
48 |
49 |
50 | https://monkeytype-readme.com/nask/beach
51 | 2023-11-30T09:31:29+00:00
52 | 0.80
53 |
54 |
55 | https://monkeytype-readme.com/davidho0403/lil_dragon
56 | 2023-11-30T09:31:29+00:00
57 | 0.80
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/public/image/apeMonkeyIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkeytype-hub/monkeytype-readme/6c105458564d3183f288b42674195cff794f721b/public/image/apeMonkeyIcon.png
--------------------------------------------------------------------------------
/public/image/github-30*30.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/image/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkeytype-hub/monkeytype-readme/6c105458564d3183f288b42674195cff794f721b/public/image/github.png
--------------------------------------------------------------------------------
/public/image/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/public/image/github.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkeytype-hub/monkeytype-readme/6c105458564d3183f288b42674195cff794f721b/public/image/github.webp
--------------------------------------------------------------------------------
/public/image/plus-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/image/stupidMonkeyIcon-30*30.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/public/image/stupidMonkeyIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkeytype-hub/monkeytype-readme/6c105458564d3183f288b42674195cff794f721b/public/image/stupidMonkeyIcon.png
--------------------------------------------------------------------------------
/public/image/stupidMonkeyIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/public/image/stupidMonkeyIcon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkeytype-hub/monkeytype-readme/6c105458564d3183f288b42674195cff794f721b/public/image/stupidMonkeyIcon.webp
--------------------------------------------------------------------------------
/public/image/stupidMonkeyIconWhite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkeytype-hub/monkeytype-readme/6c105458564d3183f288b42674195cff794f721b/public/image/stupidMonkeyIconWhite.png
--------------------------------------------------------------------------------
/public/image/stupidMonkeyIconWhite.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkeytype-hub/monkeytype-readme/6c105458564d3183f288b42674195cff794f721b/public/image/stupidMonkeyIconWhite.webp
--------------------------------------------------------------------------------
/public/image/userImg/README.md:
--------------------------------------------------------------------------------
1 | ### This file is use to save the user images. Do not edit this file directly.
2 |
--------------------------------------------------------------------------------
/public/script/generateSvg.js:
--------------------------------------------------------------------------------
1 | const { getOutputCSS } = require("./tailwindCSS");
2 |
3 | const request = require("request");
4 | const fs = require("fs");
5 |
6 | const downloadUserImg = (url, path) => {
7 | return new Promise((resolve, reject) => {
8 | request.head(url, (err, res, body) => {
9 | request(url)
10 | .pipe(fs.createWriteStream(path))
11 | .on("close", resolve)
12 | .on("error", reject);
13 | });
14 | });
15 | };
16 |
17 | const getUserImg = async (userData, theme) => {
18 | let userImg;
19 | let defaultUserImg = `
20 |
26 | `;
27 | if (
28 | userData === null ||
29 | userData.discordId === undefined ||
30 | userData.discordAvatar === undefined
31 | ) {
32 | userImg = defaultUserImg;
33 | } else {
34 | // Download the image and save it to a file
35 | const imagePath = `public/image/userImg/${userData.discordId}-${userData.discordAvatar}.png`;
36 | await downloadUserImg(
37 | `https://cdn.discordapp.com/avatars/${userData.discordId}/${userData.discordAvatar}.png?size=256`,
38 | imagePath,
39 | );
40 |
41 | // Convert the image file to base64
42 | const imageData = fs.readFileSync(imagePath);
43 | const base64Image = imageData.toString("base64");
44 |
45 | if (base64Image == "") {
46 | userImg = defaultUserImg;
47 | } else {
48 | userImg = `
49 |
50 |

51 |
52 | `;
53 | }
54 | }
55 | return userImg;
56 | };
57 |
58 | const getUserBadge = (badge, theme) => {
59 | let userBadge = "";
60 | if (badge !== null) {
61 | let color;
62 | if (badge.color === "white") color = "white";
63 | else color = theme[badge.color];
64 |
65 | badge.iconSvg = badge.iconSvg.replace('fill=""', `fill="${color}"`);
66 | userBadge = `
67 |
75 |
${badge.iconSvg}
76 |
77 | ${badge.name}
78 |
79 |
80 | `;
81 | }
82 | return userBadge;
83 | };
84 |
85 | const formatTopPercentage = (lbRank) => {
86 | if (lbRank?.rank === undefined) return "-";
87 | if (lbRank?.count === undefined) return "-";
88 | if (lbRank.rank === 1) return "GOAT";
89 | let percentage = (lbRank.rank / lbRank.count) * 100;
90 | let formattedPercentage =
91 | percentage % 1 === 0 ? percentage.toString() : percentage.toFixed(2);
92 | return "Top " + formattedPercentage + "%";
93 | };
94 |
95 | async function getOGSvg(userData, theme, badge) {
96 | const width = 500;
97 | const height = 200;
98 | const cssData = await getOutputCSS();
99 |
100 | let userImg = await getUserImg(userData, theme);
101 | let userBadge = getUserBadge(badge, theme);
102 |
103 | const svg = `
104 |
148 | `;
149 | return svg;
150 | }
151 |
152 | async function getSvg(userData, theme, badge, leaderBoards, personalbests) {
153 | const width = 500;
154 | let height = 220;
155 | leaderBoards ? (height += 220) : (height += 0);
156 | personalbests ? (height += 440) : (height += 0);
157 | const cssData = await getOutputCSS();
158 |
159 | let userImg = await getUserImg(userData, theme);
160 | let userBadge = getUserBadge(badge, theme);
161 |
162 | let leaderBoardHTML = "";
163 | if (leaderBoards == true) {
164 | topPercentage15 = formatTopPercentage(
165 | userData.allTimeLbs.time["15"]["english"],
166 | );
167 | topPercentage60 = formatTopPercentage(
168 | userData.allTimeLbs.time["60"]["english"],
169 | );
170 |
171 | const ordinalNumber = (rank) => {
172 | if (rank === undefined || rank === null) return "";
173 | if (rank % 10 === 1) return "st";
174 | if (rank % 10 === 2) return "nd";
175 | if (rank % 10 === 3) return "rd";
176 | return "th";
177 | };
178 |
179 | const allTimeLbs = userData.allTimeLbs;
180 | let rank15 = "-";
181 | let rank60 = "-";
182 | let ordinalNumber15 = "";
183 | let ordinalNumber60 = "";
184 |
185 | try {
186 | const time15 = allTimeLbs.time["15"] || {};
187 | const time60 = allTimeLbs.time["60"] || {};
188 |
189 | rank15 = !time15.english?.rank ? "-" : time15.english.rank;
190 | rank60 = !time60.english?.rank ? "-" : time60.english.rank;
191 |
192 | ordinalNumber15 =
193 | typeof time15.english?.rank === "number"
194 | ? ordinalNumber(time15.english.rank)
195 | : "-";
196 |
197 | ordinalNumber60 =
198 | typeof time60.english?.rank === "number"
199 | ? ordinalNumber(time60.english.rank)
200 | : "-";
201 | } catch (e) {
202 | console.log(e);
203 | console.log(userData);
204 | console.log(userData.allTimeLbs.time);
205 | rank15 = "-";
206 | rank60 = "-";
207 | ordinalNumber15 = "";
208 | ordinalNumber60 = "";
209 | }
210 |
211 | leaderBoardHTML = `
212 |
213 |
214 |
215 |
216 | All-Time English Leaderboards
217 |
218 |
219 |
220 |
221 |
222 |
224 | 15 seconds
225 |
226 |
228 | ${topPercentage15}
229 |
230 |
231 |
233 | ${rank15}
234 |
235 |
237 | ${ordinalNumber15}
238 |
239 |
240 |
241 |
242 |
244 | 60 seconds
245 |
246 |
248 | ${topPercentage60}
249 |
250 |
251 |
253 | ${rank60}
254 |
255 |
257 | ${ordinalNumber60}
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 | `;
266 | }
267 |
268 | let personalbestsHTML = "";
269 | if (personalbests == true) {
270 | let pbTime = {};
271 | for (let j = 15; j <= 120; j *= 2) {
272 | let english_1k = true;
273 | let english = true;
274 | let english_1k_pb = null;
275 | let english_pb = null;
276 | if (userData.personalBests.time[j] != undefined) {
277 | for (
278 | let i = 0;
279 | i < userData.personalBests.time[j].length;
280 | i++
281 | ) {
282 | if (
283 | userData.personalBests.time[j][i].language ==
284 | "english_1k" &&
285 | userData.personalBests.time[j][i].difficulty ==
286 | "normal" &&
287 | userData.personalBests.time[j][i].punctuation ==
288 | false &&
289 | english_1k == true
290 | ) {
291 | english_1k_pb = userData.personalBests.time[j][i];
292 | english_1k = false;
293 | }
294 | if (
295 | userData.personalBests.time[j][i].language ==
296 | "english" &&
297 | userData.personalBests.time[j][i].difficulty ==
298 | "normal" &&
299 | userData.personalBests.time[j][i].punctuation ==
300 | false &&
301 | english == true
302 | ) {
303 | english_pb = userData.personalBests.time[j][i];
304 | english = false;
305 | }
306 | }
307 | if (english_1k_pb == null && english_pb == null) {
308 | pbTime[j] = { wpm: "-", acc: "-" };
309 | } else if (english_1k_pb != null && english_pb == null) {
310 | pbTime[j] = english_1k_pb;
311 | } else if (english_1k_pb == null && english_pb != null) {
312 | pbTime[j] = english_pb;
313 | } else {
314 | if (english_1k_pb.wpm > english_pb.wpm) {
315 | pbTime[j] = english_1k_pb;
316 | } else {
317 | pbTime[j] = english_pb;
318 | }
319 | }
320 | } else {
321 | pbTime[j] = { wpm: "-", acc: "-" };
322 | }
323 | if (pbTime[j].wpm != "-") {
324 | pbTime[j].wpm = Math.round(parseFloat(pbTime[j].wpm));
325 | }
326 | if (pbTime[j].acc != "-") {
327 | if (pbTime[j].acc == null || pbTime[j].acc == undefined) {
328 | pbTime[j].acc = "-";
329 | } else {
330 | pbTime[j].acc = Math.floor(parseFloat(pbTime[j].acc));
331 | }
332 | }
333 | }
334 |
335 | let pbWords = {};
336 | let words = [10, 25, 50, 100];
337 | for (let i = 0; i < words.length; i++) {
338 | let english_1k = true;
339 | let english = true;
340 | let english_1k_pb = null;
341 | let english_pb = null;
342 | if (userData.personalBests.words[words[i]] != undefined) {
343 | for (
344 | let j = 0;
345 | j < userData.personalBests.words[words[i]].length;
346 | j++
347 | ) {
348 | if (
349 | userData.personalBests.words[words[i]][j].language ==
350 | "english_1k" &&
351 | userData.personalBests.words[words[i]][j].difficulty ==
352 | "normal" &&
353 | userData.personalBests.words[words[i]][j].punctuation ==
354 | false &&
355 | english_1k == true
356 | ) {
357 | english_1k_pb =
358 | userData.personalBests.words[words[i]][j];
359 | english_1k = false;
360 | }
361 | if (
362 | userData.personalBests.words[words[i]][j].language ==
363 | "english" &&
364 | userData.personalBests.words[words[i]][j].difficulty ==
365 | "normal" &&
366 | userData.personalBests.words[words[i]][j].punctuation ==
367 | false &&
368 | english == true
369 | ) {
370 | english_pb = userData.personalBests.words[words[i]][j];
371 | english = false;
372 | }
373 | }
374 | if (english_1k_pb == null && english_pb == null) {
375 | pbWords[words[i]] = { wpm: "-", acc: "-" };
376 | } else if (english_1k_pb != null && english_pb == null) {
377 | pbWords[words[i]] = english_1k_pb;
378 | } else if (english_1k_pb == null && english_pb != null) {
379 | pbWords[words[i]] = english_pb;
380 | } else {
381 | if (english_1k_pb.wpm > english_pb.wpm) {
382 | pbWords[words[i]] = english_1k_pb;
383 | } else {
384 | pbWords[words[i]] = english_pb;
385 | }
386 | }
387 | } else {
388 | pbWords[words[i]] = { wpm: "-", acc: "-" };
389 | }
390 | if (pbWords[words[i]].wpm != "-") {
391 | pbWords[words[i]].wpm = Math.round(
392 | parseFloat(pbWords[words[i]].wpm),
393 | );
394 | }
395 | if (pbWords[words[i]].acc != "-") {
396 | if (
397 | pbWords[words[i]].acc == null ||
398 | pbWords[words[i]].acc == undefined
399 | ) {
400 | pbWords[words[i]].acc = "-";
401 | } else {
402 | pbWords[words[i]].acc = Math.floor(
403 | parseFloat(pbWords[words[i]].acc),
404 | );
405 | }
406 | }
407 | }
408 |
409 | personalbestsHTML = `
410 |
413 |
414 |
415 |
416 |
417 |
419 | 15 seconds
420 |
421 |
423 | ${pbTime["15"].wpm}
424 |
425 |
427 | ${pbTime["15"].acc}${
428 | pbTime["15"].acc == "-" ? "" : "%"
429 | }
430 |
431 |
432 |
433 |
435 | 30 seconds
436 |
437 |
439 | ${pbTime["30"].wpm}
440 |
441 |
443 | ${pbTime["30"].acc}${
444 | pbTime["30"].acc == "-" ? "" : "%"
445 | }
446 |
447 |
448 |
449 |
451 | 60 seconds
452 |
453 |
455 | ${pbTime["60"].wpm}
456 |
457 |
459 | ${pbTime["60"].acc}${
460 | pbTime["60"].acc == "-" ? "" : "%"
461 | }
462 |
463 |
464 |
465 |
467 | 120 seconds
468 |
469 |
471 | ${pbTime["120"].wpm}
472 |
473 |
475 | ${pbTime["120"].acc}${
476 | pbTime["120"].acc == "-" ? "" : "%"
477 | }
478 |
479 |
480 |
481 |
482 |
483 |
484 |
487 |
488 |
489 |
490 |
491 |
493 | 10 words
494 |
495 |
497 | ${pbWords["10"].wpm}
498 |
499 |
501 | ${pbWords["10"].acc}${
502 | pbWords["10"].acc == "-" ? "" : "%"
503 | }
504 |
505 |
506 |
507 |
509 | 25 words
510 |
511 |
513 | ${pbWords["25"].wpm}
514 |
515 |
517 | ${pbWords["25"].acc}${
518 | pbWords["25"].acc == "-" ? "" : "%"
519 | }
520 |
521 |
522 |
523 |
525 | 50 words
526 |
527 |
529 | ${pbWords["50"].wpm}
530 |
531 |
533 | ${pbWords["50"].acc}${
534 | pbWords["50"].acc == "-" ? "" : "%"
535 | }
536 |
537 |
538 |
539 |
541 | 100 words
542 |
543 |
545 | ${pbWords["100"].wpm}
546 |
547 |
549 | ${pbWords["100"].acc}${
550 | pbWords["100"].acc == "-" ? "" : "%"
551 | }
552 |
553 |
554 |
555 |
556 |
557 |
558 | `;
559 | }
560 |
561 | const svg = `
562 |
612 | `;
613 | return svg;
614 | }
615 |
616 | module.exports = {
617 | getOGSvg,
618 | getSvg,
619 | };
620 |
--------------------------------------------------------------------------------
/public/script/index.js:
--------------------------------------------------------------------------------
1 | let monkeytypeName = "";
2 | let leaderBoardBtnState = false;
3 | let personalBestBtnState = false;
4 | let themeListState = {
5 | themeName: "",
6 | borderColor: "",
7 | };
8 |
9 | $("#createNowBtn").click(function () {
10 | let targetElement = $("#mr-introduce");
11 | $("html, body").animate(
12 | {
13 | scrollTop: targetElement.offset().top,
14 | },
15 | 800,
16 | );
17 | });
18 |
19 | $("#monkeytypeNameInput").on("input", function () {
20 | monkeytypeName = $(this).val();
21 | });
22 |
23 | $("#leaderBoardBtn").click(function () {
24 | initialReadmeBtn("#leaderBoardBtn", leaderBoardBtnState);
25 | leaderBoardBtnState = !leaderBoardBtnState;
26 | });
27 |
28 | $("#personalBestBtn").click(function () {
29 | initialReadmeBtn("#personalBestBtn", personalBestBtnState);
30 | personalBestBtnState = !personalBestBtnState;
31 | });
32 |
33 | $("#generateReadmeBtn").click(async function () {
34 | $("#monkeytypeNameError").addClass("absolute hidden");
35 | $("#themeNameError").addClass("absolute hidden");
36 | $("#monkeytypeNameInvalidError").addClass("absolute hidden");
37 |
38 | let svgDataCheck = false;
39 | const VALID_NAME_PATTERN = /^[\da-zA-Z_.-]+$/;
40 |
41 | if (monkeytypeName === "") {
42 | $("#monkeytypeNameError").removeClass("absolute hidden");
43 | svgDataCheck = true;
44 | }
45 |
46 | if (themeListState.themeName === "") {
47 | $("#themeNameError").removeClass("absolute hidden");
48 | svgDataCheck = true;
49 | }
50 |
51 | if (
52 | monkeytypeName !== "" &&
53 | (!VALID_NAME_PATTERN.test(monkeytypeName) ||
54 | !(monkeytypeName.length > 1 && monkeytypeName.length < 16))
55 | ) {
56 | $("#monkeytypeNameInvalidError").removeClass("absolute hidden");
57 | svgDataCheck = true;
58 | }
59 |
60 | if (svgDataCheck) {
61 | return;
62 | }
63 |
64 | $("#generateReadmeBtn").prop("disabled", true);
65 | $("#generateReadmeBtn").addClass("cursor-not-allowed");
66 | $("#generateReadmeBtn").removeClass(
67 | "hover:bg-nord-light-green hover:text-nord-light-bg hover:opacity-60",
68 | );
69 | $("#generateReadmeBtnLoad").removeClass("hidden");
70 | $("#generateReadmeBtnText").text("Monkeytype Readme Generating...");
71 |
72 | let themeList = await getMonkeyTypeThemesList();
73 | let themeData = {};
74 |
75 | for (let i = 0; i < themeList.length; i++) {
76 | if (themeListState.themeName === themeList[i]["name"]) {
77 | themeData = themeList[i];
78 | break;
79 | }
80 | }
81 |
82 | let personalReadmeUrl = `${domain}/${monkeytypeName}/${themeListState.themeName}`;
83 | let personalReadmeBtnStyle = `color: ${themeData["mainColor"]}; background-color: ${themeData["bgColor"]}; outline-color: ${themeData["mainColor"]};"`;
84 |
85 | let url = `${domain}/generate-svg/${monkeytypeName}/${themeListState.themeName}`;
86 | if (leaderBoardBtnState && personalBestBtnState) {
87 | url += "?lbpb=true";
88 | } else {
89 | if (leaderBoardBtnState) {
90 | url += "?lb=true";
91 | }
92 | if (personalBestBtnState) {
93 | url += "?pb=true";
94 | }
95 | }
96 |
97 | const img = new Image();
98 |
99 | img.src = url;
100 |
101 | img.onload = function () {
102 | $("#previewReadmeLink").attr(
103 | "href",
104 | `https://monkeytype.com/profile/${monkeytypeName}`,
105 | );
106 | $("#previewReadmeImg").attr("src", url);
107 | $("#previewReadmeImg").attr(
108 | "alt",
109 | monkeytypeName + " | Monkeytype Readme",
110 | );
111 |
112 | $("#personalReadmeLink").attr("href", personalReadmeUrl);
113 | $("#personalReadmeLink").attr(
114 | "title",
115 | `${monkeytypeName} | Monkeytype Readme`,
116 | );
117 | $("#personalReadmeBtn").attr("style", personalReadmeBtnStyle);
118 |
119 | updateReadmeCode();
120 |
121 | $("#generateReadmeBtn").prop("disabled", false);
122 | $("#generateReadmeBtn").removeClass("cursor-not-allowed");
123 | $("#generateReadmeBtn").addClass(
124 | "hover:bg-nord-light-green hover:text-nord-light-bg hover:opacity-60",
125 | );
126 |
127 | $("#personalReadmeBtn").removeClass("hidden");
128 |
129 | $("#generateReadmeBtnLoad").addClass("hidden");
130 | $("#generateReadmeBtnText").text("Generate Monkeytype Readme");
131 | };
132 | });
133 |
134 | $("#monkeytypeNameError").mouseenter(function () {
135 | $("#monkeytypeNameErrorHover").removeClass("hidden");
136 | });
137 |
138 | $("#monkeytypeNameError").mouseleave(function () {
139 | $("#monkeytypeNameErrorHover").addClass("hidden");
140 | });
141 |
142 | $("#themeNameError").mouseenter(function () {
143 | $("#themeNameErrorHover").removeClass("hidden");
144 | });
145 |
146 | $("#themeNameError").mouseleave(function () {
147 | $("#themeNameErrorHover").addClass("hidden");
148 | });
149 |
150 | $("#monkeytypeNameInvalidError").mouseenter(function () {
151 | $("#monkeytypeNameInvalidErrorHover").removeClass("hidden");
152 | });
153 |
154 | $("#monkeytypeNameInvalidError").mouseleave(function () {
155 | $("#monkeytypeNameInvalidErrorHover").addClass("hidden");
156 | });
157 |
158 | function errorHoverClick(id) {
159 | $(`#${id}`).addClass("hidden");
160 | }
161 |
162 | function initialReadmeBtn(buttonId, buttonState) {
163 | if (!buttonState) {
164 | $(buttonId).removeClass("bg-slate-100 text-gray-400");
165 | $(buttonId).addClass(
166 | "bg-nord-light-green text-nord-light-bg opacity-60",
167 | );
168 | } else {
169 | $(buttonId).removeClass(
170 | "bg-nord-light-green text-nord-light-bg opacity-60",
171 | );
172 | $(buttonId).addClass("bg-slate-100 text-gray-400");
173 | }
174 | }
175 |
176 | function showThemeList() {
177 | $("#showThemeBtn").addClass("hidden");
178 | $("#themeListContainer").removeClass("h-96");
179 | $("#hideThemeBtn").removeClass("hidden");
180 | }
181 |
182 | function hideThemeList() {
183 | let targetElement = $("#mr-create");
184 | $("html, body").animate(
185 | {
186 | scrollTop: targetElement.offset().top,
187 | },
188 | 800,
189 | );
190 | setTimeout(() => {
191 | $("#showThemeBtn").removeClass("hidden");
192 | $("#themeListContainer").addClass("h-96");
193 | $("#hideThemeBtn").addClass("hidden");
194 | }, 800);
195 | }
196 |
197 | function showBorder(themeName) {
198 | const borderColor = $(`#${themeName}`).css("border-color");
199 | if (themeName === themeListState.themeName) {
200 | $(`#${themeName}`).css("border", "");
201 | $(`#${themeName}`).css("border-color", themeListState.borderColor);
202 | themeListState.themeName = "";
203 | } else {
204 | if (themeListState.themeName !== "") {
205 | $(`#${themeListState.themeName}`).css("border", "");
206 | $(`#${themeListState.themeName}`).css(
207 | "border-color",
208 | themeListState.borderColor,
209 | );
210 | }
211 | $(`#${themeName}`).css("border", `4px solid ${borderColor}`);
212 | themeListState.themeName = themeName;
213 | themeListState.borderColor = borderColor;
214 | }
215 | }
216 |
217 | function updateReadmeCode() {
218 | const githubReamdeYml = `
219 |
220 | name: generate monkeytype readme svg
221 |
222 | on:
223 | schedule:
224 | - cron: "0 */6 * * *"
225 | workflow_dispatch:
226 |
227 | jobs:
228 | download-svg:
229 | runs-on: ubuntu-latest
230 | steps:
231 | - name: Checkout code
232 | uses: actions/checkout@v3
233 |
234 | - name: Set up Node.js
235 | uses: actions/setup-node@v3
236 | with:
237 | node-version: '16.x'
238 |
239 | - name: Download SVG
240 | run: |
241 | mkdir public
242 | curl -o public/monkeytype-readme.svg ${domain}/generate-svg/${monkeytypeName}/${themeListState.themeName}
243 | curl -o public/monkeytype-readme-lb.svg ${domain}/generate-svg/${monkeytypeName}/${themeListState.themeName}?lb=true
244 | curl -o public/monkeytype-readme-pb.svg ${domain}/generate-svg/${monkeytypeName}/${themeListState.themeName}?pb=true
245 | curl -o public/monkeytype-readme-lb-pb.svg ${domain}/generate-svg/${monkeytypeName}/${themeListState.themeName}?lbpb=true
246 |
247 | - name: push monkeytype-readme.svg to the monkeytype-readme branch
248 | uses: crazy-max/ghaction-github-pages@v2.5.0
249 | with:
250 | target_branch: monkeytype-readme
251 | build_dir: public
252 | env:
253 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
254 |
255 |
256 | `;
257 | $("#githubReadmeYml").empty();
258 | $("#githubReadmeYml").append(githubReamdeYml);
259 |
260 | const githubReamdeMd = `
261 |
262 | <a href="https://monkeytype.com/profile/${monkeytypeName}">
263 | <img src="https://raw.githubusercontent.com/GITHUB_USERNAME/GITHUB_REPOSITORY/monkeytype-readme/monkeytype-readme-lb.svg" alt="My Monkeytype profile" />
264 | </a>
265 |
266 |
267 | `;
268 | $("#githubReadmeMd").empty();
269 | $("#githubReadmeMd").append(githubReamdeMd);
270 | }
271 |
272 | function hexToRgb(hex) {
273 | // Remove the # symbol if present
274 | hex = hex.replace("#", "");
275 |
276 | // Check if the hex code is three characters long
277 | if (hex.length === 3) {
278 | // Duplicate each character to expand the code to six characters
279 | hex = hex.replace(/(.)/g, "$1$1");
280 | }
281 |
282 | // Convert the hex value to RGB
283 | const r = parseInt(hex.substring(0, 2), 16);
284 | const g = parseInt(hex.substring(2, 4), 16);
285 | const b = parseInt(hex.substring(4, 6), 16);
286 |
287 | return { r, g, b };
288 | }
289 |
290 | function compareColors(hex1, hex2, themeName1, themeName2) {
291 | const rgb1 = hexToRgb(hex1);
292 | const rgb2 = hexToRgb(hex2);
293 | const brightness1 = rgb1.r + rgb1.g + rgb1.b;
294 | const brightness2 = rgb2.r + rgb2.g + rgb2.b;
295 |
296 | if (brightness1 > brightness2) {
297 | return true;
298 | } else if (brightness1 == brightness2) {
299 | if (themeName1 < themeName2) {
300 | return true;
301 | } else {
302 | return false;
303 | }
304 | } else {
305 | return false;
306 | }
307 | }
308 |
309 | async function getMonkeyTypeThemesList() {
310 | const url =
311 | "https://raw.githubusercontent.com/monkeytype-hub/monkeytype-readme/refs/heads/master/monkeytype-data/themes.json";
312 |
313 | return fetch(url)
314 | .then((response) => response.json())
315 | .then((data) => {
316 | return data;
317 | });
318 | }
319 |
320 | async function themeList() {
321 | let themeList = await getMonkeyTypeThemesList();
322 |
323 | for (let i = 0; i < themeList.length; i++) {
324 | for (let j = i + 1; j < themeList.length; j++) {
325 | if (
326 | compareColors(
327 | themeList[i]["bgColor"],
328 | themeList[j]["bgColor"],
329 | themeList[i]["name"],
330 | themeList[j]["name"],
331 | )
332 | ) {
333 | let temp = themeList[i];
334 | themeList[i] = themeList[j];
335 | themeList[j] = temp;
336 | }
337 | }
338 | }
339 |
340 | for (let i = themeList.length - 1; i >= 0; i--) {
341 | let html = `
342 |
354 | `;
355 | $("#themeListContainer").append(html);
356 | }
357 | }
358 |
359 | themeList();
360 |
--------------------------------------------------------------------------------
/public/script/monkeytypeData.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | let fetch;
3 | import("node-fetch")
4 | .then((module) => {
5 | fetch = module.default;
6 | })
7 | .catch((err) => {
8 | console.error("Error while importing node-fetch:", err);
9 | });
10 |
11 | const { library } = require("@fortawesome/fontawesome-svg-core");
12 | const { fas } = require("@fortawesome/free-solid-svg-icons");
13 | const { far } = require("@fortawesome/free-regular-svg-icons");
14 | const { fab } = require("@fortawesome/free-brands-svg-icons");
15 | const { findIconDefinition } = require("@fortawesome/fontawesome-svg-core");
16 |
17 | library.add(fas, far, fab);
18 |
19 | function getTheme(themeName) {
20 | const themesRawData = fs.readFileSync("./monkeytype-data/themes.json");
21 | const themesData = JSON.parse(themesRawData);
22 | let serika_dark = {};
23 |
24 | for (let i = 0; i < themesData.length; i++) {
25 | const theme = themesData[i];
26 | if (theme.name === themeName) {
27 | return theme;
28 | }
29 | if (theme.name === "serika_dark") {
30 | serika_dark = theme;
31 | }
32 | }
33 |
34 | return serika_dark;
35 | }
36 |
37 | function getFaviconTheme() {
38 | const themesRawData = fs.readFileSync("./monkeytype-data/themes.json");
39 | const themesData = JSON.parse(themesRawData);
40 | let faviconData = {};
41 |
42 | let data;
43 | for (let i = 0; i < themesData.length; i++) {
44 | data = {
45 | name: themesData[i].name,
46 | bgColor: themesData[i].bgColor,
47 | mainColor: themesData[i].mainColor,
48 | subColor: themesData[i].subColor,
49 | textColor: themesData[i].textColor,
50 | };
51 | faviconData[i] = data;
52 | }
53 |
54 | return faviconData;
55 | }
56 |
57 | function getBadge(badgeId) {
58 | const badgesRawData = fs.readFileSync("./monkeytype-data/badges.json");
59 | const badgesData = JSON.parse(badgesRawData);
60 | return badgesData[badgeId];
61 | }
62 |
63 | async function getUserData(userId) {
64 | const url = `https://api.monkeytype.com/users/${userId}/profile`;
65 |
66 | try {
67 | return fetch(url)
68 | .then((response) => response.json())
69 | .then((data) => {
70 | return data.data;
71 | });
72 | } catch (error) {
73 | console.error(error);
74 | }
75 | }
76 |
77 | async function getMonkeyTypeThemesData() {
78 | const url = "https://mr-api.zeabur.app/themes";
79 |
80 | try {
81 | return fetch(url)
82 | .then((response) => response.json())
83 | .then((data) => {
84 | return data;
85 | });
86 | } catch (error) {
87 | console.error(error);
88 | }
89 | }
90 |
91 | async function getMonkeyTypeBadgesData() {
92 | const url =
93 | "https://raw.githubusercontent.com/monkeytypegame/monkeytype/master/frontend/src/ts/controllers/badge-controller.ts";
94 |
95 | return fetch(url)
96 | .then((response) => response.text())
97 | .then((data) => {
98 | const badgesStart = data.search(
99 | "const badges: Record = {",
100 | );
101 | const badgesDataStart =
102 | badgesStart +
103 | "const badges: Record = ".length;
104 | const badgesDataEnd = data.indexOf("};", badgesDataStart);
105 | let badgesData = data.substring(badgesDataStart, badgesDataEnd + 1);
106 | badgesData = badgesData
107 | .replace(/(\w+)\s*:/g, '"$1":')
108 | .replace(/,(\s*[\]}])/g, "$1")
109 | .replace(
110 | /(\w+:)|(\w+ :)/g,
111 | (matchedStr) => '"' + matchedStr.replace(/:/g, "") + '":',
112 | )
113 | .replace(/\"/g, '"')
114 | .replace(
115 | /"customStyle"\s*:\s*"([^"]*\"animation\"[^"]*\"background\"[^"]*)"/g,
116 | (match, customStyleValue) => {
117 | const updatedCustomStyle = customStyleValue
118 | .replace(/\"animation\"/g, "animation")
119 | .replace(/\"background\"/g, "background");
120 | return `"customStyle": "${updatedCustomStyle}"`;
121 | },
122 | );
123 | badgesData = JSON.parse(badgesData);
124 |
125 | for (let i = 0; i < Object.keys(badgesData).length; i++) {
126 | let badge = badgesData[Object.keys(badgesData)[i]];
127 | if (badge.color.includes("var")) {
128 | badge.color = badge.color
129 | .replace("var(--", "")
130 | .replace(")", "")
131 | .replace("-", "")
132 | .replace("c", "C");
133 | }
134 | if (badge.background && badge.background.includes("var")) {
135 | badge.background = badge.background
136 | .replace("var(--", "")
137 | .replace(")", "")
138 | .replace("-", "")
139 | .replace("c", "C");
140 | }
141 | const iconSvg = findIconDefinition({
142 | prefix: "fas",
143 | iconName: `${badge.icon.replace("fa-", "")}`,
144 | });
145 | badge[
146 | "iconSvg"
147 | ] = ``;
148 | }
149 |
150 | badgesData = JSON.stringify(badgesData, null, 4);
151 |
152 | return badgesData;
153 | })
154 | .catch((error) => console.error(error));
155 | }
156 |
157 | module.exports = {
158 | getTheme,
159 | getFaviconTheme,
160 | getBadge,
161 | getUserData,
162 | getMonkeyTypeThemesData,
163 | getMonkeyTypeBadgesData,
164 | };
165 |
--------------------------------------------------------------------------------
/public/script/tailwindCSS.js:
--------------------------------------------------------------------------------
1 | const util = require("util");
2 | const fs = require("fs");
3 | const readFile = util.promisify(fs.readFile);
4 |
5 | async function getOutputCSS() {
6 | try {
7 | const data = await readFile("public/style/output.css", "utf-8");
8 | return data;
9 | } catch (err) {
10 | console.error(err);
11 | return;
12 | }
13 | }
14 |
15 | module.exports = {
16 | getOutputCSS,
17 | };
18 |
--------------------------------------------------------------------------------
/public/script/user.js:
--------------------------------------------------------------------------------
1 | let shareListState = false;
2 |
3 | $("#shareBtn").click(function () {
4 | if (!shareListState) {
5 | $("#shareList").removeClass("hidden");
6 | shareListState = true;
7 | } else {
8 | $("#shareList").addClass("hidden");
9 | shareListState = false;
10 | }
11 | });
12 |
13 | $(document).click(function (event) {
14 | if (
15 | !$(event.target).closest(
16 | "#shareTwitterBtn, #shareFacebookBtn, #shareUrlBtn, #shareBtn",
17 | ).length &&
18 | shareListState == true
19 | ) {
20 | $("#shareList").addClass("hidden");
21 | shareListState = false;
22 | }
23 | });
24 |
25 | async function copyReadmeUrl(copyUrl) {
26 | await navigator.clipboard.writeText(copyUrl);
27 | }
28 |
--------------------------------------------------------------------------------
/public/style/input.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/public/style/output.css:
--------------------------------------------------------------------------------
1 | /*
2 | ! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com
3 | */
4 |
5 | /*
6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
8 | */
9 |
10 | *,
11 | ::before,
12 | ::after {
13 | box-sizing: border-box;
14 | /* 1 */
15 | border-width: 0;
16 | /* 2 */
17 | border-style: solid;
18 | /* 2 */
19 | border-color: #e5e7eb;
20 | /* 2 */
21 | }
22 |
23 | ::before,
24 | ::after {
25 | --tw-content: '';
26 | }
27 |
28 | /*
29 | 1. Use a consistent sensible line-height in all browsers.
30 | 2. Prevent adjustments of font size after orientation changes in iOS.
31 | 3. Use a more readable tab size.
32 | 4. Use the user's configured `sans` font-family by default.
33 | 5. Use the user's configured `sans` font-feature-settings by default.
34 | 6. Use the user's configured `sans` font-variation-settings by default.
35 | */
36 |
37 | html {
38 | line-height: 1.5;
39 | /* 1 */
40 | -webkit-text-size-adjust: 100%;
41 | /* 2 */
42 | -moz-tab-size: 4;
43 | /* 3 */
44 | -o-tab-size: 4;
45 | tab-size: 4;
46 | /* 3 */
47 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
48 | /* 4 */
49 | font-feature-settings: normal;
50 | /* 5 */
51 | font-variation-settings: normal;
52 | /* 6 */
53 | }
54 |
55 | /*
56 | 1. Remove the margin in all browsers.
57 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
58 | */
59 |
60 | body {
61 | margin: 0;
62 | /* 1 */
63 | line-height: inherit;
64 | /* 2 */
65 | }
66 |
67 | /*
68 | 1. Add the correct height in Firefox.
69 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
70 | 3. Ensure horizontal rules are visible by default.
71 | */
72 |
73 | hr {
74 | height: 0;
75 | /* 1 */
76 | color: inherit;
77 | /* 2 */
78 | border-top-width: 1px;
79 | /* 3 */
80 | }
81 |
82 | /*
83 | Add the correct text decoration in Chrome, Edge, and Safari.
84 | */
85 |
86 | abbr:where([title]) {
87 | -webkit-text-decoration: underline dotted;
88 | text-decoration: underline dotted;
89 | }
90 |
91 | /*
92 | Remove the default font size and weight for headings.
93 | */
94 |
95 | h1,
96 | h2,
97 | h3,
98 | h4,
99 | h5,
100 | h6 {
101 | font-size: inherit;
102 | font-weight: inherit;
103 | }
104 |
105 | /*
106 | Reset links to optimize for opt-in styling instead of opt-out.
107 | */
108 |
109 | a {
110 | color: inherit;
111 | text-decoration: inherit;
112 | }
113 |
114 | /*
115 | Add the correct font weight in Edge and Safari.
116 | */
117 |
118 | b,
119 | strong {
120 | font-weight: bolder;
121 | }
122 |
123 | /*
124 | 1. Use the user's configured `mono` font family by default.
125 | 2. Correct the odd `em` font sizing in all browsers.
126 | */
127 |
128 | code,
129 | kbd,
130 | samp,
131 | pre {
132 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
133 | /* 1 */
134 | font-size: 1em;
135 | /* 2 */
136 | }
137 |
138 | /*
139 | Add the correct font size in all browsers.
140 | */
141 |
142 | small {
143 | font-size: 80%;
144 | }
145 |
146 | /*
147 | Prevent `sub` and `sup` elements from affecting the line height in all browsers.
148 | */
149 |
150 | sub,
151 | sup {
152 | font-size: 75%;
153 | line-height: 0;
154 | position: relative;
155 | vertical-align: baseline;
156 | }
157 |
158 | sub {
159 | bottom: -0.25em;
160 | }
161 |
162 | sup {
163 | top: -0.5em;
164 | }
165 |
166 | /*
167 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
168 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
169 | 3. Remove gaps between table borders by default.
170 | */
171 |
172 | table {
173 | text-indent: 0;
174 | /* 1 */
175 | border-color: inherit;
176 | /* 2 */
177 | border-collapse: collapse;
178 | /* 3 */
179 | }
180 |
181 | /*
182 | 1. Change the font styles in all browsers.
183 | 2. Remove the margin in Firefox and Safari.
184 | 3. Remove default padding in all browsers.
185 | */
186 |
187 | button,
188 | input,
189 | optgroup,
190 | select,
191 | textarea {
192 | font-family: inherit;
193 | /* 1 */
194 | font-feature-settings: inherit;
195 | /* 1 */
196 | font-variation-settings: inherit;
197 | /* 1 */
198 | font-size: 100%;
199 | /* 1 */
200 | font-weight: inherit;
201 | /* 1 */
202 | line-height: inherit;
203 | /* 1 */
204 | color: inherit;
205 | /* 1 */
206 | margin: 0;
207 | /* 2 */
208 | padding: 0;
209 | /* 3 */
210 | }
211 |
212 | /*
213 | Remove the inheritance of text transform in Edge and Firefox.
214 | */
215 |
216 | button,
217 | select {
218 | text-transform: none;
219 | }
220 |
221 | /*
222 | 1. Correct the inability to style clickable types in iOS and Safari.
223 | 2. Remove default button styles.
224 | */
225 |
226 | button,
227 | [type='button'],
228 | [type='reset'],
229 | [type='submit'] {
230 | -webkit-appearance: button;
231 | /* 1 */
232 | background-color: transparent;
233 | /* 2 */
234 | background-image: none;
235 | /* 2 */
236 | }
237 |
238 | /*
239 | Use the modern Firefox focus style for all focusable elements.
240 | */
241 |
242 | :-moz-focusring {
243 | outline: auto;
244 | }
245 |
246 | /*
247 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
248 | */
249 |
250 | :-moz-ui-invalid {
251 | box-shadow: none;
252 | }
253 |
254 | /*
255 | Add the correct vertical alignment in Chrome and Firefox.
256 | */
257 |
258 | progress {
259 | vertical-align: baseline;
260 | }
261 |
262 | /*
263 | Correct the cursor style of increment and decrement buttons in Safari.
264 | */
265 |
266 | ::-webkit-inner-spin-button,
267 | ::-webkit-outer-spin-button {
268 | height: auto;
269 | }
270 |
271 | /*
272 | 1. Correct the odd appearance in Chrome and Safari.
273 | 2. Correct the outline style in Safari.
274 | */
275 |
276 | [type='search'] {
277 | -webkit-appearance: textfield;
278 | /* 1 */
279 | outline-offset: -2px;
280 | /* 2 */
281 | }
282 |
283 | /*
284 | Remove the inner padding in Chrome and Safari on macOS.
285 | */
286 |
287 | ::-webkit-search-decoration {
288 | -webkit-appearance: none;
289 | }
290 |
291 | /*
292 | 1. Correct the inability to style clickable types in iOS and Safari.
293 | 2. Change font properties to `inherit` in Safari.
294 | */
295 |
296 | ::-webkit-file-upload-button {
297 | -webkit-appearance: button;
298 | /* 1 */
299 | font: inherit;
300 | /* 2 */
301 | }
302 |
303 | /*
304 | Add the correct display in Chrome and Safari.
305 | */
306 |
307 | summary {
308 | display: list-item;
309 | }
310 |
311 | /*
312 | Removes the default spacing and border for appropriate elements.
313 | */
314 |
315 | blockquote,
316 | dl,
317 | dd,
318 | h1,
319 | h2,
320 | h3,
321 | h4,
322 | h5,
323 | h6,
324 | hr,
325 | figure,
326 | p,
327 | pre {
328 | margin: 0;
329 | }
330 |
331 | fieldset {
332 | margin: 0;
333 | padding: 0;
334 | }
335 |
336 | legend {
337 | padding: 0;
338 | }
339 |
340 | ol,
341 | ul,
342 | menu {
343 | list-style: none;
344 | margin: 0;
345 | padding: 0;
346 | }
347 |
348 | /*
349 | Reset default styling for dialogs.
350 | */
351 |
352 | dialog {
353 | padding: 0;
354 | }
355 |
356 | /*
357 | Prevent resizing textareas horizontally by default.
358 | */
359 |
360 | textarea {
361 | resize: vertical;
362 | }
363 |
364 | /*
365 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
366 | 2. Set the default placeholder color to the user's configured gray 400 color.
367 | */
368 |
369 | input::-moz-placeholder, textarea::-moz-placeholder {
370 | opacity: 1;
371 | /* 1 */
372 | color: #9ca3af;
373 | /* 2 */
374 | }
375 |
376 | input::placeholder,
377 | textarea::placeholder {
378 | opacity: 1;
379 | /* 1 */
380 | color: #9ca3af;
381 | /* 2 */
382 | }
383 |
384 | /*
385 | Set the default cursor for buttons.
386 | */
387 |
388 | button,
389 | [role="button"] {
390 | cursor: pointer;
391 | }
392 |
393 | /*
394 | Make sure disabled buttons don't get the pointer cursor.
395 | */
396 |
397 | :disabled {
398 | cursor: default;
399 | }
400 |
401 | /*
402 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
403 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
404 | This can trigger a poorly considered lint error in some tools but is included by design.
405 | */
406 |
407 | img,
408 | svg,
409 | video,
410 | canvas,
411 | audio,
412 | iframe,
413 | embed,
414 | object {
415 | display: block;
416 | /* 1 */
417 | vertical-align: middle;
418 | /* 2 */
419 | }
420 |
421 | /*
422 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
423 | */
424 |
425 | img,
426 | video {
427 | max-width: 100%;
428 | height: auto;
429 | }
430 |
431 | /* Make elements with the HTML hidden attribute stay hidden by default */
432 |
433 | [hidden] {
434 | display: none;
435 | }
436 |
437 | *, ::before, ::after {
438 | --tw-border-spacing-x: 0;
439 | --tw-border-spacing-y: 0;
440 | --tw-translate-x: 0;
441 | --tw-translate-y: 0;
442 | --tw-rotate: 0;
443 | --tw-skew-x: 0;
444 | --tw-skew-y: 0;
445 | --tw-scale-x: 1;
446 | --tw-scale-y: 1;
447 | --tw-pan-x: ;
448 | --tw-pan-y: ;
449 | --tw-pinch-zoom: ;
450 | --tw-scroll-snap-strictness: proximity;
451 | --tw-gradient-from-position: ;
452 | --tw-gradient-via-position: ;
453 | --tw-gradient-to-position: ;
454 | --tw-ordinal: ;
455 | --tw-slashed-zero: ;
456 | --tw-numeric-figure: ;
457 | --tw-numeric-spacing: ;
458 | --tw-numeric-fraction: ;
459 | --tw-ring-inset: ;
460 | --tw-ring-offset-width: 0px;
461 | --tw-ring-offset-color: #fff;
462 | --tw-ring-color: rgb(59 130 246 / 0.5);
463 | --tw-ring-offset-shadow: 0 0 #0000;
464 | --tw-ring-shadow: 0 0 #0000;
465 | --tw-shadow: 0 0 #0000;
466 | --tw-shadow-colored: 0 0 #0000;
467 | --tw-blur: ;
468 | --tw-brightness: ;
469 | --tw-contrast: ;
470 | --tw-grayscale: ;
471 | --tw-hue-rotate: ;
472 | --tw-invert: ;
473 | --tw-saturate: ;
474 | --tw-sepia: ;
475 | --tw-drop-shadow: ;
476 | --tw-backdrop-blur: ;
477 | --tw-backdrop-brightness: ;
478 | --tw-backdrop-contrast: ;
479 | --tw-backdrop-grayscale: ;
480 | --tw-backdrop-hue-rotate: ;
481 | --tw-backdrop-invert: ;
482 | --tw-backdrop-opacity: ;
483 | --tw-backdrop-saturate: ;
484 | --tw-backdrop-sepia: ;
485 | }
486 |
487 | ::backdrop {
488 | --tw-border-spacing-x: 0;
489 | --tw-border-spacing-y: 0;
490 | --tw-translate-x: 0;
491 | --tw-translate-y: 0;
492 | --tw-rotate: 0;
493 | --tw-skew-x: 0;
494 | --tw-skew-y: 0;
495 | --tw-scale-x: 1;
496 | --tw-scale-y: 1;
497 | --tw-pan-x: ;
498 | --tw-pan-y: ;
499 | --tw-pinch-zoom: ;
500 | --tw-scroll-snap-strictness: proximity;
501 | --tw-gradient-from-position: ;
502 | --tw-gradient-via-position: ;
503 | --tw-gradient-to-position: ;
504 | --tw-ordinal: ;
505 | --tw-slashed-zero: ;
506 | --tw-numeric-figure: ;
507 | --tw-numeric-spacing: ;
508 | --tw-numeric-fraction: ;
509 | --tw-ring-inset: ;
510 | --tw-ring-offset-width: 0px;
511 | --tw-ring-offset-color: #fff;
512 | --tw-ring-color: rgb(59 130 246 / 0.5);
513 | --tw-ring-offset-shadow: 0 0 #0000;
514 | --tw-ring-shadow: 0 0 #0000;
515 | --tw-shadow: 0 0 #0000;
516 | --tw-shadow-colored: 0 0 #0000;
517 | --tw-blur: ;
518 | --tw-brightness: ;
519 | --tw-contrast: ;
520 | --tw-grayscale: ;
521 | --tw-hue-rotate: ;
522 | --tw-invert: ;
523 | --tw-saturate: ;
524 | --tw-sepia: ;
525 | --tw-drop-shadow: ;
526 | --tw-backdrop-blur: ;
527 | --tw-backdrop-brightness: ;
528 | --tw-backdrop-contrast: ;
529 | --tw-backdrop-grayscale: ;
530 | --tw-backdrop-hue-rotate: ;
531 | --tw-backdrop-invert: ;
532 | --tw-backdrop-opacity: ;
533 | --tw-backdrop-saturate: ;
534 | --tw-backdrop-sepia: ;
535 | }
536 |
537 | .pointer-events-none {
538 | pointer-events: none;
539 | }
540 |
541 | .static {
542 | position: static;
543 | }
544 |
545 | .absolute {
546 | position: absolute;
547 | }
548 |
549 | .relative {
550 | position: relative;
551 | }
552 |
553 | .-top-16 {
554 | top: -4rem;
555 | }
556 |
557 | .bottom-0 {
558 | bottom: 0px;
559 | }
560 |
561 | .left-0 {
562 | left: 0px;
563 | }
564 |
565 | .right-0 {
566 | right: 0px;
567 | }
568 |
569 | .top-0 {
570 | top: 0px;
571 | }
572 |
573 | .isolate {
574 | isolation: isolate;
575 | }
576 |
577 | .z-10 {
578 | z-index: 10;
579 | }
580 |
581 | .col-span-1 {
582 | grid-column: span 1 / span 1;
583 | }
584 |
585 | .col-span-5 {
586 | grid-column: span 5 / span 5;
587 | }
588 |
589 | .mx-1 {
590 | margin-left: 0.25rem;
591 | margin-right: 0.25rem;
592 | }
593 |
594 | .mx-2 {
595 | margin-left: 0.5rem;
596 | margin-right: 0.5rem;
597 | }
598 |
599 | .mx-5 {
600 | margin-left: 1.25rem;
601 | margin-right: 1.25rem;
602 | }
603 |
604 | .my-16 {
605 | margin-top: 4rem;
606 | margin-bottom: 4rem;
607 | }
608 |
609 | .-mt-3 {
610 | margin-top: -0.75rem;
611 | }
612 |
613 | .mb-10 {
614 | margin-bottom: 2.5rem;
615 | }
616 |
617 | .mb-3 {
618 | margin-bottom: 0.75rem;
619 | }
620 |
621 | .ml-2 {
622 | margin-left: 0.5rem;
623 | }
624 |
625 | .ml-3 {
626 | margin-left: 0.75rem;
627 | }
628 |
629 | .ml-4 {
630 | margin-left: 1rem;
631 | }
632 |
633 | .ml-6 {
634 | margin-left: 1.5rem;
635 | }
636 |
637 | .mr-2 {
638 | margin-right: 0.5rem;
639 | }
640 |
641 | .mt-0 {
642 | margin-top: 0px;
643 | }
644 |
645 | .mt-0\.5 {
646 | margin-top: 0.125rem;
647 | }
648 |
649 | .mt-1 {
650 | margin-top: 0.25rem;
651 | }
652 |
653 | .mt-16 {
654 | margin-top: 4rem;
655 | }
656 |
657 | .mt-2 {
658 | margin-top: 0.5rem;
659 | }
660 |
661 | .mt-3 {
662 | margin-top: 0.75rem;
663 | }
664 |
665 | .mt-4 {
666 | margin-top: 1rem;
667 | }
668 |
669 | .mt-44 {
670 | margin-top: 11rem;
671 | }
672 |
673 | .mt-5 {
674 | margin-top: 1.25rem;
675 | }
676 |
677 | .mt-6 {
678 | margin-top: 1.5rem;
679 | }
680 |
681 | .mt-8 {
682 | margin-top: 2rem;
683 | }
684 |
685 | .block {
686 | display: block;
687 | }
688 |
689 | .flex {
690 | display: flex;
691 | }
692 |
693 | .grid {
694 | display: grid;
695 | }
696 |
697 | .hidden {
698 | display: none;
699 | }
700 |
701 | .h-1\/6 {
702 | height: 16.666667%;
703 | }
704 |
705 | .h-10 {
706 | height: 2.5rem;
707 | }
708 |
709 | .h-12 {
710 | height: 3rem;
711 | }
712 |
713 | .h-14 {
714 | height: 3.5rem;
715 | }
716 |
717 | .h-16 {
718 | height: 4rem;
719 | }
720 |
721 | .h-20 {
722 | height: 5rem;
723 | }
724 |
725 | .h-5\/6 {
726 | height: 83.333333%;
727 | }
728 |
729 | .h-60 {
730 | height: 15rem;
731 | }
732 |
733 | .h-8 {
734 | height: 2rem;
735 | }
736 |
737 | .h-96 {
738 | height: 24rem;
739 | }
740 |
741 | .h-\[85\%\] {
742 | height: 85%;
743 | }
744 |
745 | .h-full {
746 | height: 100%;
747 | }
748 |
749 | .h-screen {
750 | height: 100vh;
751 | }
752 |
753 | .w-10 {
754 | width: 2.5rem;
755 | }
756 |
757 | .w-20 {
758 | width: 5rem;
759 | }
760 |
761 | .w-26 {
762 | width: 6.5rem;
763 | }
764 |
765 | .w-28 {
766 | width: 7rem;
767 | }
768 |
769 | .w-32 {
770 | width: 8rem;
771 | }
772 |
773 | .w-33 {
774 | width: 8.25rem;
775 | }
776 |
777 | .w-8 {
778 | width: 2rem;
779 | }
780 |
781 | .w-fit {
782 | width: -moz-fit-content;
783 | width: fit-content;
784 | }
785 |
786 | .w-full {
787 | width: 100%;
788 | }
789 |
790 | @keyframes bounce {
791 | 0%, 100% {
792 | transform: translateY(-25%);
793 | animation-timing-function: cubic-bezier(0.8,0,1,1);
794 | }
795 |
796 | 50% {
797 | transform: none;
798 | animation-timing-function: cubic-bezier(0,0,0.2,1);
799 | }
800 | }
801 |
802 | .animate-bounce {
803 | animation: bounce 1s infinite;
804 | }
805 |
806 | @keyframes fade-left {
807 | 0% {
808 | opacity: 0;
809 | transform: translateX(2rem);
810 | }
811 |
812 | 100% {
813 | opacity: 1;
814 | transform: translateX(0);
815 | }
816 | }
817 |
818 | .animate-fade-left {
819 | animation: fade-left 1s both;
820 | }
821 |
822 | @keyframes fade-right {
823 | 0% {
824 | opacity: 0;
825 | transform: translateX(-2rem);
826 | }
827 |
828 | 100% {
829 | opacity: 1;
830 | transform: translateX(0);
831 | }
832 | }
833 |
834 | .animate-fade-right {
835 | animation: fade-right 1s both;
836 | }
837 |
838 | @keyframes fade-up {
839 | 0% {
840 | opacity: 0;
841 | transform: translateY(2rem);
842 | }
843 |
844 | 100% {
845 | opacity: 1;
846 | transform: translateY(0);
847 | }
848 | }
849 |
850 | .animate-fade-up {
851 | animation: fade-up 1s both;
852 | }
853 |
854 | @keyframes rgb-bg {
855 | 0% {
856 | background-color: hsl(120, 39%, 49%);
857 | }
858 |
859 | 20% {
860 | background-color: hsl(192, 48%, 48%);
861 | }
862 |
863 | 40% {
864 | background-color: hsl(264, 90%, 58%);
865 | }
866 |
867 | 60% {
868 | background-color: hsl(357, 89%, 50%);
869 | }
870 |
871 | 80% {
872 | background-color: hsl(46, 100%, 51%);
873 | }
874 |
875 | 100% {
876 | background-color: hsl(120, 39%, 49%);
877 | }
878 | }
879 |
880 | .animate-rgb-bg {
881 | animation: rgb-bg 10s linear infinite;
882 | }
883 |
884 | @keyframes spin {
885 | to {
886 | transform: rotate(360deg);
887 | }
888 | }
889 |
890 | .animate-spin {
891 | animation: spin 1s linear infinite;
892 | }
893 |
894 | .cursor-not-allowed {
895 | cursor: not-allowed;
896 | }
897 |
898 | .grid-cols-1 {
899 | grid-template-columns: repeat(1, minmax(0, 1fr));
900 | }
901 |
902 | .grid-cols-3 {
903 | grid-template-columns: repeat(3, minmax(0, 1fr));
904 | }
905 |
906 | .flex-col {
907 | flex-direction: column;
908 | }
909 |
910 | .items-end {
911 | align-items: flex-end;
912 | }
913 |
914 | .items-center {
915 | align-items: center;
916 | }
917 |
918 | .justify-start {
919 | justify-content: flex-start;
920 | }
921 |
922 | .justify-center {
923 | justify-content: center;
924 | }
925 |
926 | .justify-between {
927 | justify-content: space-between;
928 | }
929 |
930 | .justify-around {
931 | justify-content: space-around;
932 | }
933 |
934 | .gap-2 {
935 | gap: 0.5rem;
936 | }
937 |
938 | .gap-4 {
939 | gap: 1rem;
940 | }
941 |
942 | .gap-x-5 {
943 | -moz-column-gap: 1.25rem;
944 | column-gap: 1.25rem;
945 | }
946 |
947 | .overflow-hidden {
948 | overflow: hidden;
949 | }
950 |
951 | .rounded-2xl {
952 | border-radius: 1rem;
953 | }
954 |
955 | .rounded-full {
956 | border-radius: 9999px;
957 | }
958 |
959 | .rounded-lg {
960 | border-radius: 0.5rem;
961 | }
962 |
963 | .rounded-md {
964 | border-radius: 0.375rem;
965 | }
966 |
967 | .rounded-xl {
968 | border-radius: 0.75rem;
969 | }
970 |
971 | .border {
972 | border-width: 1px;
973 | }
974 |
975 | .border-4 {
976 | border-width: 4px;
977 | }
978 |
979 | .border-nord-light-green {
980 | --tw-border-opacity: 1;
981 | border-color: rgb(143 188 187 / var(--tw-border-opacity));
982 | }
983 |
984 | .border-nord-light-subAlt {
985 | --tw-border-opacity: 1;
986 | border-color: rgb(216 222 233 / var(--tw-border-opacity));
987 | }
988 |
989 | .border-transparent {
990 | border-color: transparent;
991 | }
992 |
993 | .bg-black {
994 | --tw-bg-opacity: 1;
995 | background-color: rgb(0 0 0 / var(--tw-bg-opacity));
996 | }
997 |
998 | .bg-code-bg-brown {
999 | --tw-bg-opacity: 1;
1000 | background-color: rgb(245 242 240 / var(--tw-bg-opacity));
1001 | }
1002 |
1003 | .bg-gray-900 {
1004 | --tw-bg-opacity: 1;
1005 | background-color: rgb(17 24 39 / var(--tw-bg-opacity));
1006 | }
1007 |
1008 | .bg-nord-light-error {
1009 | --tw-bg-opacity: 1;
1010 | background-color: rgb(191 97 106 / var(--tw-bg-opacity));
1011 | }
1012 |
1013 | .bg-nord-light-green {
1014 | --tw-bg-opacity: 1;
1015 | background-color: rgb(143 188 187 / var(--tw-bg-opacity));
1016 | }
1017 |
1018 | .bg-red-50 {
1019 | --tw-bg-opacity: 1;
1020 | background-color: rgb(254 242 242 / var(--tw-bg-opacity));
1021 | }
1022 |
1023 | .bg-slate-100 {
1024 | --tw-bg-opacity: 1;
1025 | background-color: rgb(241 245 249 / var(--tw-bg-opacity));
1026 | }
1027 |
1028 | .bg-white {
1029 | --tw-bg-opacity: 1;
1030 | background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1031 | }
1032 |
1033 | .bg-opacity-20 {
1034 | --tw-bg-opacity: 0.2;
1035 | }
1036 |
1037 | .bg-opacity-40 {
1038 | --tw-bg-opacity: 0.4;
1039 | }
1040 |
1041 | .bg-gradient-to-br {
1042 | background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
1043 | }
1044 |
1045 | .from-nord-light-subAlt {
1046 | --tw-gradient-from: #d8dee9 var(--tw-gradient-from-position);
1047 | --tw-gradient-to: rgb(216 222 233 / 0) var(--tw-gradient-to-position);
1048 | --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1049 | }
1050 |
1051 | .to-nord-light-green {
1052 | --tw-gradient-to: #8fbcbb var(--tw-gradient-to-position);
1053 | }
1054 |
1055 | .bg-clip-text {
1056 | -webkit-background-clip: text;
1057 | background-clip: text;
1058 | }
1059 |
1060 | .p-1 {
1061 | padding: 0.25rem;
1062 | }
1063 |
1064 | .p-2 {
1065 | padding: 0.5rem;
1066 | }
1067 |
1068 | .p-4 {
1069 | padding: 1rem;
1070 | }
1071 |
1072 | .p-6 {
1073 | padding: 1.5rem;
1074 | }
1075 |
1076 | .px-1 {
1077 | padding-left: 0.25rem;
1078 | padding-right: 0.25rem;
1079 | }
1080 |
1081 | .px-2 {
1082 | padding-left: 0.5rem;
1083 | padding-right: 0.5rem;
1084 | }
1085 |
1086 | .px-3 {
1087 | padding-left: 0.75rem;
1088 | padding-right: 0.75rem;
1089 | }
1090 |
1091 | .px-4 {
1092 | padding-left: 1rem;
1093 | padding-right: 1rem;
1094 | }
1095 |
1096 | .px-6 {
1097 | padding-left: 1.5rem;
1098 | padding-right: 1.5rem;
1099 | }
1100 |
1101 | .py-1 {
1102 | padding-top: 0.25rem;
1103 | padding-bottom: 0.25rem;
1104 | }
1105 |
1106 | .py-2 {
1107 | padding-top: 0.5rem;
1108 | padding-bottom: 0.5rem;
1109 | }
1110 |
1111 | .py-3 {
1112 | padding-top: 0.75rem;
1113 | padding-bottom: 0.75rem;
1114 | }
1115 |
1116 | .pb-16 {
1117 | padding-bottom: 4rem;
1118 | }
1119 |
1120 | .pb-4 {
1121 | padding-bottom: 1rem;
1122 | }
1123 |
1124 | .pl-1 {
1125 | padding-left: 0.25rem;
1126 | }
1127 |
1128 | .pr-2 {
1129 | padding-right: 0.5rem;
1130 | }
1131 |
1132 | .pr-5 {
1133 | padding-right: 1.25rem;
1134 | }
1135 |
1136 | .pt-20 {
1137 | padding-top: 5rem;
1138 | }
1139 |
1140 | .pt-4 {
1141 | padding-top: 1rem;
1142 | }
1143 |
1144 | .text-left {
1145 | text-align: left;
1146 | }
1147 |
1148 | .text-center {
1149 | text-align: center;
1150 | }
1151 |
1152 | .text-right {
1153 | text-align: right;
1154 | }
1155 |
1156 | .align-middle {
1157 | vertical-align: middle;
1158 | }
1159 |
1160 | .font-mono {
1161 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
1162 | }
1163 |
1164 | .text-2xl {
1165 | font-size: 1.5rem;
1166 | line-height: 2rem;
1167 | }
1168 |
1169 | .text-3xl {
1170 | font-size: 1.875rem;
1171 | line-height: 2.25rem;
1172 | }
1173 |
1174 | .text-4xl {
1175 | font-size: 2.25rem;
1176 | line-height: 2.5rem;
1177 | }
1178 |
1179 | .text-base {
1180 | font-size: 1rem;
1181 | line-height: 1.5rem;
1182 | }
1183 |
1184 | .text-lg {
1185 | font-size: 1.125rem;
1186 | line-height: 1.75rem;
1187 | }
1188 |
1189 | .text-sm {
1190 | font-size: 0.875rem;
1191 | line-height: 1.25rem;
1192 | }
1193 |
1194 | .text-xs {
1195 | font-size: 0.75rem;
1196 | line-height: 1rem;
1197 | }
1198 |
1199 | .font-bold {
1200 | font-weight: 700;
1201 | }
1202 |
1203 | .font-extrabold {
1204 | font-weight: 800;
1205 | }
1206 |
1207 | .font-medium {
1208 | font-weight: 500;
1209 | }
1210 |
1211 | .font-normal {
1212 | font-weight: 400;
1213 | }
1214 |
1215 | .font-semibold {
1216 | font-weight: 600;
1217 | }
1218 |
1219 | .not-italic {
1220 | font-style: normal;
1221 | }
1222 |
1223 | .leading-loose {
1224 | line-height: 2;
1225 | }
1226 |
1227 | .tracking-wide {
1228 | letter-spacing: 0.025em;
1229 | }
1230 |
1231 | .tracking-wider {
1232 | letter-spacing: 0.05em;
1233 | }
1234 |
1235 | .text-code-bg-black {
1236 | --tw-text-opacity: 1;
1237 | color: rgb(153 153 153 / var(--tw-text-opacity));
1238 | }
1239 |
1240 | .text-gray-400 {
1241 | --tw-text-opacity: 1;
1242 | color: rgb(156 163 175 / var(--tw-text-opacity));
1243 | }
1244 |
1245 | .text-gray-600 {
1246 | --tw-text-opacity: 1;
1247 | color: rgb(75 85 99 / var(--tw-text-opacity));
1248 | }
1249 |
1250 | .text-gray-900 {
1251 | --tw-text-opacity: 1;
1252 | color: rgb(17 24 39 / var(--tw-text-opacity));
1253 | }
1254 |
1255 | .text-nord-light-bg {
1256 | --tw-text-opacity: 1;
1257 | color: rgb(236 239 244 / var(--tw-text-opacity));
1258 | }
1259 |
1260 | .text-nord-light-green {
1261 | --tw-text-opacity: 1;
1262 | color: rgb(143 188 187 / var(--tw-text-opacity));
1263 | }
1264 |
1265 | .text-nord-light-sub {
1266 | --tw-text-opacity: 1;
1267 | color: rgb(106 119 145 / var(--tw-text-opacity));
1268 | }
1269 |
1270 | .text-rose-400 {
1271 | --tw-text-opacity: 1;
1272 | color: rgb(251 113 133 / var(--tw-text-opacity));
1273 | }
1274 |
1275 | .text-slate-400 {
1276 | --tw-text-opacity: 1;
1277 | color: rgb(148 163 184 / var(--tw-text-opacity));
1278 | }
1279 |
1280 | .text-transparent {
1281 | color: transparent;
1282 | }
1283 |
1284 | .text-white {
1285 | --tw-text-opacity: 1;
1286 | color: rgb(255 255 255 / var(--tw-text-opacity));
1287 | }
1288 |
1289 | .underline {
1290 | text-decoration-line: underline;
1291 | }
1292 |
1293 | .opacity-60 {
1294 | opacity: 0.6;
1295 | }
1296 |
1297 | .opacity-70 {
1298 | opacity: 0.7;
1299 | }
1300 |
1301 | .shadow-sm {
1302 | --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
1303 | --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
1304 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1305 | }
1306 |
1307 | .outline {
1308 | outline-style: solid;
1309 | }
1310 |
1311 | .transition {
1312 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
1313 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
1314 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
1315 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1316 | transition-duration: 150ms;
1317 | }
1318 |
1319 | .transition-opacity {
1320 | transition-property: opacity;
1321 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1322 | transition-duration: 150ms;
1323 | }
1324 |
1325 | .duration-300 {
1326 | transition-duration: 300ms;
1327 | }
1328 |
1329 | .animate-delay-\[150ms\] {
1330 | animation-delay: 150ms;
1331 | }
1332 |
1333 | .animate-delay-\[200ms\] {
1334 | animation-delay: 200ms;
1335 | }
1336 |
1337 | .animate-duration-1000 {
1338 | animation-duration: 1000ms;
1339 | }
1340 |
1341 | .animate-once {
1342 | animation-iteration-count: 1;
1343 | }
1344 |
1345 | .hover\:bg-nord-light-green:hover {
1346 | --tw-bg-opacity: 1;
1347 | background-color: rgb(143 188 187 / var(--tw-bg-opacity));
1348 | }
1349 |
1350 | .hover\:text-nord-light-bg:hover {
1351 | --tw-text-opacity: 1;
1352 | color: rgb(236 239 244 / var(--tw-text-opacity));
1353 | }
1354 |
1355 | .hover\:opacity-60:hover {
1356 | opacity: 0.6;
1357 | }
1358 |
1359 | .focus\:border-4:focus {
1360 | border-width: 4px;
1361 | }
1362 |
1363 | .focus\:border-opacity-60:focus {
1364 | --tw-border-opacity: 0.6;
1365 | }
1366 |
1367 | .focus\:outline-none:focus {
1368 | outline: 2px solid transparent;
1369 | outline-offset: 2px;
1370 | }
1371 |
1372 | @media (min-width: 640px) {
1373 | .sm\:ml-3 {
1374 | margin-left: 0.75rem;
1375 | }
1376 |
1377 | .sm\:mt-6 {
1378 | margin-top: 1.5rem;
1379 | }
1380 |
1381 | .sm\:flex {
1382 | display: flex;
1383 | }
1384 |
1385 | .sm\:text-4xl {
1386 | font-size: 2.25rem;
1387 | line-height: 2.5rem;
1388 | }
1389 | }
1390 |
1391 | @media (min-width: 768px) {
1392 | .md\:mx-0 {
1393 | margin-left: 0px;
1394 | margin-right: 0px;
1395 | }
1396 |
1397 | .md\:my-2 {
1398 | margin-top: 0.5rem;
1399 | margin-bottom: 0.5rem;
1400 | }
1401 |
1402 | .md\:mt-0 {
1403 | margin-top: 0px;
1404 | }
1405 |
1406 | .md\:mt-4 {
1407 | margin-top: 1rem;
1408 | }
1409 |
1410 | .md\:mt-8 {
1411 | margin-top: 2rem;
1412 | }
1413 |
1414 | .md\:block {
1415 | display: block;
1416 | }
1417 |
1418 | .md\:flex {
1419 | display: flex;
1420 | }
1421 |
1422 | .md\:h-16 {
1423 | height: 4rem;
1424 | }
1425 |
1426 | .md\:grid-cols-2 {
1427 | grid-template-columns: repeat(2, minmax(0, 1fr));
1428 | }
1429 |
1430 | .md\:flex-col {
1431 | flex-direction: column;
1432 | }
1433 |
1434 | .md\:px-10 {
1435 | padding-left: 2.5rem;
1436 | padding-right: 2.5rem;
1437 | }
1438 |
1439 | .md\:text-3xl {
1440 | font-size: 1.875rem;
1441 | line-height: 2.25rem;
1442 | }
1443 |
1444 | .md\:text-5xl {
1445 | font-size: 3rem;
1446 | line-height: 1;
1447 | }
1448 |
1449 | .md\:text-base {
1450 | font-size: 1rem;
1451 | line-height: 1.5rem;
1452 | }
1453 |
1454 | .md\:text-lg {
1455 | font-size: 1.125rem;
1456 | line-height: 1.75rem;
1457 | }
1458 | }
1459 |
1460 | @media (min-width: 1024px) {
1461 | .lg\:my-4 {
1462 | margin-top: 1rem;
1463 | margin-bottom: 1rem;
1464 | }
1465 |
1466 | .lg\:mb-5 {
1467 | margin-bottom: 1.25rem;
1468 | }
1469 |
1470 | .lg\:ml-2 {
1471 | margin-left: 0.5rem;
1472 | }
1473 |
1474 | .lg\:mt-0 {
1475 | margin-top: 0px;
1476 | }
1477 |
1478 | .lg\:mt-5 {
1479 | margin-top: 1.25rem;
1480 | }
1481 |
1482 | .lg\:flex {
1483 | display: flex;
1484 | }
1485 |
1486 | .lg\:grid {
1487 | display: grid;
1488 | }
1489 |
1490 | .lg\:h-10 {
1491 | height: 2.5rem;
1492 | }
1493 |
1494 | .lg\:h-12 {
1495 | height: 3rem;
1496 | }
1497 |
1498 | .lg\:h-64 {
1499 | height: 16rem;
1500 | }
1501 |
1502 | .lg\:h-\[75\%\] {
1503 | height: 75%;
1504 | }
1505 |
1506 | .lg\:h-\[calc\(100\%-1\.25rem\)\] {
1507 | height: calc(100% - 1.25rem);
1508 | }
1509 |
1510 | .lg\:w-10 {
1511 | width: 2.5rem;
1512 | }
1513 |
1514 | .lg\:w-12 {
1515 | width: 3rem;
1516 | }
1517 |
1518 | .lg\:w-28 {
1519 | width: 7rem;
1520 | }
1521 |
1522 | .lg\:grid-cols-2 {
1523 | grid-template-columns: repeat(2, minmax(0, 1fr));
1524 | }
1525 |
1526 | .lg\:grid-cols-3 {
1527 | grid-template-columns: repeat(3, minmax(0, 1fr));
1528 | }
1529 |
1530 | .lg\:gap-4 {
1531 | gap: 1rem;
1532 | }
1533 |
1534 | .lg\:border {
1535 | border-width: 1px;
1536 | }
1537 |
1538 | .lg\:border-slate-200 {
1539 | --tw-border-opacity: 1;
1540 | border-color: rgb(226 232 240 / var(--tw-border-opacity));
1541 | }
1542 |
1543 | .lg\:bg-slate-100 {
1544 | --tw-bg-opacity: 1;
1545 | background-color: rgb(241 245 249 / var(--tw-bg-opacity));
1546 | }
1547 |
1548 | .lg\:p-12 {
1549 | padding: 3rem;
1550 | }
1551 |
1552 | .lg\:px-1 {
1553 | padding-left: 0.25rem;
1554 | padding-right: 0.25rem;
1555 | }
1556 |
1557 | .lg\:px-1\.5 {
1558 | padding-left: 0.375rem;
1559 | padding-right: 0.375rem;
1560 | }
1561 |
1562 | .lg\:px-14 {
1563 | padding-left: 3.5rem;
1564 | padding-right: 3.5rem;
1565 | }
1566 |
1567 | .lg\:py-0 {
1568 | padding-top: 0px;
1569 | padding-bottom: 0px;
1570 | }
1571 |
1572 | .lg\:py-0\.5 {
1573 | padding-top: 0.125rem;
1574 | padding-bottom: 0.125rem;
1575 | }
1576 |
1577 | .lg\:pt-16 {
1578 | padding-top: 4rem;
1579 | }
1580 |
1581 | .lg\:text-2xl {
1582 | font-size: 1.5rem;
1583 | line-height: 2rem;
1584 | }
1585 |
1586 | .lg\:text-base {
1587 | font-size: 1rem;
1588 | line-height: 1.5rem;
1589 | }
1590 |
1591 | .lg\:text-xl {
1592 | font-size: 1.25rem;
1593 | line-height: 1.75rem;
1594 | }
1595 |
1596 | .lg\:font-bold {
1597 | font-weight: 700;
1598 | }
1599 |
1600 | .lg\:leading-\[3rem\] {
1601 | line-height: 3rem;
1602 | }
1603 |
1604 | .lg\:hover\:bg-nord-light-green:hover {
1605 | --tw-bg-opacity: 1;
1606 | background-color: rgb(143 188 187 / var(--tw-bg-opacity));
1607 | }
1608 |
1609 | .lg\:hover\:text-nord-light-bg:hover {
1610 | --tw-text-opacity: 1;
1611 | color: rgb(236 239 244 / var(--tw-text-opacity));
1612 | }
1613 |
1614 | .lg\:hover\:opacity-60:hover {
1615 | opacity: 0.6;
1616 | }
1617 |
1618 | .group:hover .lg\:group-hover\:block {
1619 | display: block;
1620 | }
1621 | }
1622 |
1623 | @media (min-width: 1280px) {
1624 | .xl\:grid-cols-4 {
1625 | grid-template-columns: repeat(4, minmax(0, 1fr));
1626 | }
1627 |
1628 | .xl\:text-6xl {
1629 | font-size: 3.75rem;
1630 | line-height: 1;
1631 | }
1632 |
1633 | .xl\:text-lg {
1634 | font-size: 1.125rem;
1635 | line-height: 1.75rem;
1636 | }
1637 | }
1638 |
--------------------------------------------------------------------------------
/public/style/prism.css:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.29.0
2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+yaml&plugins=line-highlight+line-numbers+show-language+toolbar+copy-to-clipboard */
3 | code[class*="language-"],
4 | pre[class*="language-"] {
5 | color: #000;
6 | background: 0 0;
7 | text-shadow: 0 1px #fff;
8 | font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
9 | font-size: 1em;
10 | text-align: left;
11 | white-space: pre;
12 | word-spacing: normal;
13 | word-break: normal;
14 | word-wrap: normal;
15 | line-height: 1.5;
16 | -moz-tab-size: 4;
17 | -o-tab-size: 4;
18 | tab-size: 4;
19 | -webkit-hyphens: none;
20 | -moz-hyphens: none;
21 | -ms-hyphens: none;
22 | hyphens: none;
23 | }
24 |
25 | code[class*="language-"] ::-moz-selection,
26 | code[class*="language-"]::-moz-selection,
27 | pre[class*="language-"] ::-moz-selection,
28 | pre[class*="language-"]::-moz-selection {
29 | text-shadow: none;
30 | background: #b3d4fc;
31 | }
32 |
33 | code[class*="language-"] ::selection,
34 | code[class*="language-"]::selection,
35 | pre[class*="language-"] ::selection,
36 | pre[class*="language-"]::selection {
37 | text-shadow: none;
38 | background: #b3d4fc;
39 | }
40 |
41 | @media print {
42 | code[class*="language-"],
43 | pre[class*="language-"] {
44 | text-shadow: none;
45 | }
46 | }
47 |
48 | pre[class*="language-"] {
49 | padding: 1em;
50 | margin: 0.5em 0;
51 | overflow: auto;
52 | }
53 |
54 | :not(pre) > code[class*="language-"],
55 | pre[class*="language-"] {
56 | background: #f5f2f0;
57 | }
58 |
59 | :not(pre) > code[class*="language-"] {
60 | padding: 0.1em;
61 | border-radius: 0.3em;
62 | white-space: normal;
63 | }
64 |
65 | .token.cdata,
66 | .token.comment,
67 | .token.doctype,
68 | .token.prolog {
69 | color: #708090;
70 | }
71 |
72 | .token.punctuation {
73 | color: #999;
74 | }
75 |
76 | .token.namespace {
77 | opacity: 0.7;
78 | }
79 |
80 | .token.boolean,
81 | .token.constant,
82 | .token.deleted,
83 | .token.number,
84 | .token.property,
85 | .token.symbol,
86 | .token.tag {
87 | color: #905;
88 | }
89 |
90 | .token.attr-name,
91 | .token.builtin,
92 | .token.char,
93 | .token.inserted,
94 | .token.selector,
95 | .token.string {
96 | color: #690;
97 | }
98 |
99 | .language-css .token.string,
100 | .style .token.string,
101 | .token.entity,
102 | .token.operator,
103 | .token.url {
104 | color: #9a6e3a;
105 | background: hsla(0, 0%, 100%, 0.5);
106 | }
107 |
108 | .token.atrule,
109 | .token.attr-value,
110 | .token.keyword {
111 | color: #07a;
112 | }
113 |
114 | .token.class-name,
115 | .token.function {
116 | color: #dd4a68;
117 | }
118 |
119 | .token.important,
120 | .token.regex,
121 | .token.variable {
122 | color: #e90;
123 | }
124 |
125 | .token.bold,
126 | .token.important {
127 | font-weight: 700;
128 | }
129 |
130 | .token.italic {
131 | font-style: italic;
132 | }
133 |
134 | .token.entity {
135 | cursor: help;
136 | }
137 |
138 | pre[data-line] {
139 | position: relative;
140 | padding: 1em 0 1em 3em;
141 | }
142 |
143 | .line-highlight {
144 | position: absolute;
145 | left: 0;
146 | right: 0;
147 | padding: inherit 0;
148 | margin-top: 1em;
149 | background: hsla(24, 20%, 50%, 0.08);
150 | background: linear-gradient(
151 | to right,
152 | hsla(24, 20%, 50%, 0.1) 70%,
153 | hsla(24, 20%, 50%, 0)
154 | );
155 | pointer-events: none;
156 | line-height: inherit;
157 | white-space: pre;
158 | }
159 |
160 | @media print {
161 | .line-highlight {
162 | -webkit-print-color-adjust: exact;
163 | color-adjust: exact;
164 | }
165 | }
166 |
167 | .line-highlight:before,
168 | .line-highlight[data-end]:after {
169 | content: attr(data-start);
170 | position: absolute;
171 | top: 0.4em;
172 | left: 0.6em;
173 | min-width: 1em;
174 | padding: 0 0.5em;
175 | background-color: hsla(24, 20%, 50%, 0.4);
176 | color: #f4f1ef;
177 | font: bold 65%/1.5 sans-serif;
178 | text-align: center;
179 | vertical-align: 0.3em;
180 | border-radius: 999px;
181 | text-shadow: none;
182 | box-shadow: 0 1px #fff;
183 | }
184 |
185 | .line-highlight[data-end]:after {
186 | content: attr(data-end);
187 | top: auto;
188 | bottom: 0.4em;
189 | }
190 |
191 | .line-numbers .line-highlight:after,
192 | .line-numbers .line-highlight:before {
193 | content: none;
194 | }
195 |
196 | pre[id].linkable-line-numbers span.line-numbers-rows {
197 | pointer-events: all;
198 | }
199 |
200 | pre[id].linkable-line-numbers span.line-numbers-rows > span:before {
201 | cursor: pointer;
202 | }
203 |
204 | pre[id].linkable-line-numbers span.line-numbers-rows > span:hover:before {
205 | background-color: rgba(128, 128, 128, 0.2);
206 | }
207 |
208 | pre[class*="language-"].line-numbers {
209 | position: relative;
210 | padding-left: 3em;
211 | counter-reset: linenumber;
212 | }
213 |
214 | pre[class*="language-"].line-numbers > code {
215 | position: relative;
216 | white-space: inherit;
217 | }
218 |
219 | .line-numbers .line-numbers-rows {
220 | position: absolute;
221 | pointer-events: none;
222 | top: 24px;
223 | font-size: 100%;
224 | left: -16em;
225 | width: 3em;
226 | letter-spacing: -1px;
227 | /* border-right: 1px solid #999; */
228 | -webkit-user-select: none;
229 | -moz-user-select: none;
230 | -ms-user-select: none;
231 | user-select: none;
232 | }
233 |
234 | .line-numbers-rows > span {
235 | display: block;
236 | counter-increment: linenumber;
237 | }
238 |
239 | .line-numbers-rows > span:before {
240 | content: counter(linenumber);
241 | color: #999;
242 | display: block;
243 | padding-right: 0.8em;
244 | text-align: right;
245 | }
246 |
247 | div.code-toolbar {
248 | position: relative;
249 | }
250 |
251 | div.code-toolbar > .toolbar {
252 | position: absolute;
253 | z-index: 10;
254 | top: 0.3em;
255 | right: 0.2em;
256 | transition: opacity 0.3s ease-in-out;
257 | opacity: 0;
258 | }
259 |
260 | div.code-toolbar:hover > .toolbar {
261 | opacity: 1;
262 | }
263 |
264 | div.code-toolbar:focus-within > .toolbar {
265 | opacity: 1;
266 | }
267 |
268 | div.code-toolbar > .toolbar > .toolbar-item {
269 | display: inline-block;
270 | }
271 |
272 | div.code-toolbar > .toolbar > .toolbar-item > a {
273 | cursor: pointer;
274 | }
275 |
276 | div.code-toolbar > .toolbar > .toolbar-item > button {
277 | background: 0 0;
278 | border: 0;
279 | color: inherit;
280 | font: inherit;
281 | line-height: normal;
282 | overflow: visible;
283 | padding: 0;
284 | -webkit-user-select: none;
285 | -moz-user-select: none;
286 | -ms-user-select: none;
287 | }
288 |
289 | div.code-toolbar > .toolbar > .toolbar-item > a,
290 | div.code-toolbar > .toolbar > .toolbar-item > button,
291 | div.code-toolbar > .toolbar > .toolbar-item > span {
292 | color: #bbb;
293 | font-size: 0.8em;
294 | margin: 5px;
295 | padding: 10px 20px;
296 | background: #f5f2f0;
297 | background: rgb(255, 255, 255);
298 | box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2);
299 | border-radius: 0.75em;
300 | }
301 |
302 | div.code-toolbar > .toolbar > .toolbar-item > a:focus,
303 | div.code-toolbar > .toolbar > .toolbar-item > a:hover,
304 | div.code-toolbar > .toolbar > .toolbar-item > button:focus,
305 | div.code-toolbar > .toolbar > .toolbar-item > button:hover,
306 | div.code-toolbar > .toolbar > .toolbar-item > span:focus,
307 | div.code-toolbar > .toolbar > .toolbar-item > span:hover {
308 | color: inherit;
309 | text-decoration: none;
310 | }
311 |
--------------------------------------------------------------------------------
/public/views/favicon.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 | MonkeyType Favicon
18 |
19 |
20 |
21 |
22 |
23 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/public/views/logo.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 |
17 | MonkeyType Logo
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/public/views/user.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
18 |
19 |
20 |
21 |
22 | <%= data.userId %> | MonkeyType Readme
23 |
27 |
28 |
29 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
47 |
51 |
55 |
59 |
60 |
61 |
62 |
66 |
70 |
74 |
78 |
79 |
80 |
81 |
82 |
83 |
124 |
125 |
126 |
127 |
128 |
263 |
357 |
358 |

364 |
365 |
385 |
388 |
389 |
390 |
408 |
424 |
428 |
438 |
439 |
440 |
441 |
445 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./*.{html,js}", "./public/views/*.html", "./public/views/*.ejs"],
4 | theme: {
5 | extend: {
6 | colors: {
7 | "nord-light-green": "#8fbcbb",
8 | "nord-light-bg": "#eceff4",
9 | "nord-light-subAlt": "#d8dee9",
10 | "nord-light-sub": "#6a7791",
11 | "nord-light-error": "#bf616a",
12 | "code-bg-brown": "#f5f2f0",
13 | "code-bg-black": "#999",
14 | },
15 | width: {
16 | 26: "6.5rem",
17 | 33: "8.25rem",
18 | 34: "8.5rem",
19 | },
20 | margin: {
21 | 38: "9.5rem",
22 | },
23 | animation: {
24 | "rgb-bg": "rgb-bg 10s linear infinite",
25 | },
26 | keyframes: {
27 | "rgb-bg": {
28 | "0%": { "background-color": "hsl(120, 39%, 49%)" },
29 | "20%": { "background-color": "hsl(192, 48%, 48%)" },
30 | "40%": { "background-color": "hsl(264, 90%, 58%)" },
31 | "60%": { "background-color": "hsl(357, 89%, 50%)" },
32 | "80%": { "background-color": "hsl(46, 100%, 51%)" },
33 | "100%": { "background-color": "hsl(120, 39%, 49%)" },
34 | },
35 | },
36 | },
37 | },
38 | plugins: [require("tailwindcss-animated")],
39 | };
40 |
--------------------------------------------------------------------------------