Compare commits

..

18 Commits

Author SHA1 Message Date
Derek 4a93c2272b (basic) MFM support 2022-07-23 13:08:35 -04:00
Derek 06adc19ea9 Cleanup websocket connection handling 2022-03-01 10:34:37 -07:00
Derek 6d4bc1dac1 Better event cards! 2022-02-26 22:59:03 -07:00
Derek 3149a48913 Add some handy aliases 2022-02-26 21:37:32 -07:00
Derek 0db073f651 Fix bug where ws listeners would duplicate 2022-02-21 18:08:22 -07:00
Derek 1a1a4be3f8 Increase message timeout 2022-02-20 22:05:27 -07:00
Derek a613f28dd0 Add placeholder follow and sub events 2022-02-20 22:04:14 -07:00
Derek 8ca7d95a97 Add basic raid indicator 2022-02-19 15:19:39 -07:00
Derek bcd09d2521 Add basic suport for media in messages 2022-02-15 18:32:04 -07:00
Derek 285c86ed39 Dont attempt to bypass the websocket connect
Caused duplications if it went multiple loops without connecting and then connected
2022-02-15 18:31:20 -07:00
Derek b2e32aa105 Set text size everywhere 2022-02-01 23:34:15 -07:00
Derek 81d909d5db Improve websocket connection 2022-02-01 23:34:05 -07:00
Derek 370b845036 Images in da chat!!!!!!!!!!!!! 2022-02-01 23:33:44 -07:00
Derek 24b5dd835e Better handling of emotes 2022-01-30 10:19:34 -07:00
Derek bf56ce9034 Use woff2 font 2022-01-30 03:24:45 -07:00
Derek 22ef8bb29c Better fade out 2022-01-30 03:16:03 -07:00
Derek 14ec6550fa Emote support!!! 2022-01-30 03:15:54 -07:00
Derek 0083793ba3 Animations and MRO checking! 2022-01-30 02:18:15 -07:00
48 changed files with 1338 additions and 337 deletions

12
config-overrides.js Normal file
View File

@ -0,0 +1,12 @@
const {
override,
addWebpackAlias,
} = require("customize-cra");
const path = require("path");
module.exports = override(
addWebpackAlias({
'@': path.resolve(__dirname, 'src/'),
'@components': path.resolve(__dirname, 'src/components'),
}),
);

220
package-lock.json generated
View File

@ -11,10 +11,16 @@
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"canvas-confetti": "^1.5.1",
"customize-cra": "^1.0.0",
"mfm-js": "^0.22.1",
"polished": "^4.1.3",
"react": "^17.0.2",
"react-app-rewired": "^2.2.1",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.1",
"react-scripts": "5.0.0",
"react-transition-group": "^4.4.2",
"styled-components": "^5.3.3"
}
},
@ -4894,6 +4900,15 @@
"url": "https://opencollective.com/browserslist"
}
},
"node_modules/canvas-confetti": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.5.1.tgz",
"integrity": "sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==",
"funding": {
"type": "donate",
"url": "https://www.paypal.me/kirilvatev"
}
},
"node_modules/case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz",
@ -5717,6 +5732,19 @@
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
},
"node_modules/csstype": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
},
"node_modules/customize-cra": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/customize-cra/-/customize-cra-1.0.0.tgz",
"integrity": "sha512-DbtaLuy59224U+xCiukkxSq8clq++MOtJ1Et7LED1fLszWe88EoblEYFBJ895sB1mC6B4uu3xPT/IjClELhMbA==",
"dependencies": {
"lodash.flow": "^3.5.0"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz",
@ -6008,6 +6036,15 @@
"utila": "~0.4"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
@ -7932,6 +7969,14 @@
"he": "bin/he"
}
},
"node_modules/history": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
"integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
"dependencies": {
"@babel/runtime": "^7.7.6"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -10787,6 +10832,11 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
"node_modules/lodash.flow": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz",
"integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o="
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -10933,6 +10983,14 @@
"node": ">= 0.6"
}
},
"node_modules/mfm-js": {
"version": "0.22.1",
"resolved": "https://registry.npmjs.org/mfm-js/-/mfm-js-0.22.1.tgz",
"integrity": "sha512-UV5zvDKlWPpBFeABhyCzuOTJ3RwrNrmVpJ+zz/dFX6D/ntEywljgxkfsLamcy0ZSwUAr0O+WQxGHvAwyxUgsAQ==",
"dependencies": {
"twemoji-parser": "14.0.x"
}
},
"node_modules/micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
@ -13131,6 +13189,28 @@
"node": ">=14"
}
},
"node_modules/react-app-rewired": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.2.1.tgz",
"integrity": "sha512-uFQWTErXeLDrMzOJHKp0h8P1z0LV9HzPGsJ6adOtGlA/B9WfT6Shh4j2tLTTGlXOfiVx6w6iWpp7SOC5pvk+gA==",
"dependencies": {
"semver": "^5.6.0"
},
"bin": {
"react-app-rewired": "bin/index.js"
},
"peerDependencies": {
"react-scripts": ">=2.1.3"
}
},
"node_modules/react-app-rewired/node_modules/semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/react-dev-utils": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz",
@ -13279,6 +13359,30 @@
"node": ">=0.10.0"
}
},
"node_modules/react-router": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
"integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
"dependencies": {
"history": "^5.2.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
"integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
"dependencies": {
"history": "^5.2.0",
"react-router": "6.2.1"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-scripts": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
@ -13351,6 +13455,21 @@
}
}
},
"node_modules/react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@ -14991,6 +15110,11 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/twemoji-parser": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -19669,6 +19793,11 @@
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001294.tgz",
"integrity": "sha512-LiMlrs1nSKZ8qkNhpUf5KD0Al1KCBE3zaT7OLOwEkagXMEDij98SiOovn9wxVGQpklk9vVC/pUSqgYmkmKOS8g=="
},
"canvas-confetti": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.5.1.tgz",
"integrity": "sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg=="
},
"case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz",
@ -20269,6 +20398,19 @@
}
}
},
"csstype": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
},
"customize-cra": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/customize-cra/-/customize-cra-1.0.0.tgz",
"integrity": "sha512-DbtaLuy59224U+xCiukkxSq8clq++MOtJ1Et7LED1fLszWe88EoblEYFBJ895sB1mC6B4uu3xPT/IjClELhMbA==",
"requires": {
"lodash.flow": "^3.5.0"
}
},
"damerau-levenshtein": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz",
@ -20496,6 +20638,15 @@
"utila": "~0.4"
}
},
"dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"requires": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"dom-serializer": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
@ -21878,6 +22029,14 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"history": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
"integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
"requires": {
"@babel/runtime": "^7.7.6"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -23943,6 +24102,11 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
"lodash.flow": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz",
"integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -24061,6 +24225,14 @@
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mfm-js": {
"version": "0.22.1",
"resolved": "https://registry.npmjs.org/mfm-js/-/mfm-js-0.22.1.tgz",
"integrity": "sha512-UV5zvDKlWPpBFeABhyCzuOTJ3RwrNrmVpJ+zz/dFX6D/ntEywljgxkfsLamcy0ZSwUAr0O+WQxGHvAwyxUgsAQ==",
"requires": {
"twemoji-parser": "14.0.x"
}
},
"micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
@ -25489,6 +25661,21 @@
"whatwg-fetch": "^3.6.2"
}
},
"react-app-rewired": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.2.1.tgz",
"integrity": "sha512-uFQWTErXeLDrMzOJHKp0h8P1z0LV9HzPGsJ6adOtGlA/B9WfT6Shh4j2tLTTGlXOfiVx6w6iWpp7SOC5pvk+gA==",
"requires": {
"semver": "^5.6.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
}
},
"react-dev-utils": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz",
@ -25600,6 +25787,23 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
},
"react-router": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
"integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
"requires": {
"history": "^5.2.0"
}
},
"react-router-dom": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
"integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
"requires": {
"history": "^5.2.0",
"react-router": "6.2.1"
}
},
"react-scripts": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
@ -25655,6 +25859,17 @@
"workbox-webpack-plugin": "^6.4.1"
}
},
"react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@ -26874,6 +27089,11 @@
}
}
},
"twemoji-parser": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -6,17 +6,22 @@
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"canvas-confetti": "^1.5.1",
"customize-cra": "^1.0.0",
"mfm-js": "^0.22.1",
"polished": "^4.1.3",
"react": "^17.0.2",
"react-app-rewired": "^2.2.1",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.1",
"react-scripts": "5.0.0",
"react-transition-group": "^4.4.2",
"styled-components": "^5.3.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
},
"eslintConfig": {
"extends": [

View File

@ -0,0 +1,42 @@
import { useRef, useCallback, useEffect } from 'react';
import confetti from 'canvas-confetti';
const commonOptions = (tier) => ({
ticks: 400,
particleCount: 25 * tier,
});
const delay = 1000;
export default function useZoom(count, tier) {
const canvasRef = useRef();
const doAnimation = useCallback(() => {
const confettiCannon = confetti.create(canvasRef.current, { useWorker: true });
let remainCount = count;
const pop = () => {
confettiCannon({
origin: { x: 0, y: 1 },
angle: 45,
drift: -0.5,
...commonOptions(tier),
});
confettiCannon({
origin: { x: 1, y: 1 },
angle: 135,
drift: 0.5,
...commonOptions(tier),
});
remainCount -= 1;
if (remainCount > 0) {
setTimeout(pop, Math.max(delay / remainCount, 200));
}
};
pop();
}, [canvasRef]);
return [canvasRef, doAnimation];
}

79
src/animations/useZoom.js Normal file
View File

@ -0,0 +1,79 @@
import { useRef, useCallback, useEffect } from 'react';
import XTERM_COLORS from '../xterm';
const RAIDERS_PER_SECOND = 5;
const MAX_SECONDS = 10;
const FPS_CAP = 120;
const LINE_HEIGHT = 5;
const LINE_WIDTH = 700;
// Speed is in "seconds to cross card width"
const START_SPEED = 0.06;
const MIN_SPEED = 0.2;
export default function useZoom(count) {
const canvasRef = useRef();
const doAnimation = useCallback(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
const height = canvas.height;
const width = canvas.width;
let delay = count / RAIDERS_PER_SECOND;
if (delay > MAX_SECONDS) { delay = MAX_SECONDS };
const lines = Array(count).fill(null).map(() => ({
y: Math.floor(Math.random() * height),
x: width + LINE_WIDTH,
offset: Math.floor(Math.random() * delay * 1000),
speed: width / START_SPEED,
decel: Math.random() * (width / (MIN_SPEED - START_SPEED)),
color: XTERM_COLORS[118 + Math.floor(Math.random() * 24)],
}));
const fpsInterval = FPS_CAP / 1000;
const start = Date.now();
let then = Date.now();
let now, dt, elapsed;
let animationFrameId = requestAnimationFrame(animStep);
function animStep() {
ctx.clearRect(0, 0, width, height);
animationFrameId = requestAnimationFrame(animStep);
let alive = false;
now = Date.now();
dt = now - then;
if (dt < fpsInterval) return;
then = now - (dt % fpsInterval);
elapsed = now - start;
dt = dt / 1000;
lines.forEach((line, i) => {
if (line.x < 0) { return; }
alive = true;
if (line.offset > elapsed) return;
const nextX = line.x - (dt * line.speed);
ctx.beginPath();
ctx.lineWidth = LINE_HEIGHT;
ctx.strokeStyle = line.color;
ctx.moveTo(line.x, line.y);
ctx.lineTo(nextX - LINE_WIDTH, line.y);
ctx.stroke();
ctx.closePath();
line.x = nextX;
if (line.speed - line.decel > (width / MIN_SPEED)) {
line.speed -= line.decel;
}
});
if (!alive) {
cancelAnimationFrame(animationFrameId);
ctx.clearRect(0, 0, width, height);
}
};
}, [canvasRef, count]);
return [canvasRef, doAnimation];
}

View File

@ -1,36 +1,50 @@
import { useEffect, useState, useRef } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { useQuery } from '../../hooks';
import ChatLog from '../chat-log';
const maxsize = 100;
const maxsize = 25;
function App() {
const [messages, setMessages] = useState([]);
const socket = useRef(null);
const [messages, setMessages] = useState(Array(maxsize).fill(null));
const [socket, setSocket] = useState();
const query = useQuery();
const websocketAddress = query.get('ws') ?? 'ws://127.0.0.1:8080';
const onEvent = useCallback((event) => {
setMessages((state) => {
const newState = [...state];
newState.unshift(JSON.parse(event.data));
newState.pop();
return newState;
});
}, []);
useEffect(() => {
const onEvent = function (event) {
setMessages((state) => {
if (state.length >= maxsize) {
state.pop();
if (socket) {
const onDiscconect = () => setSocket(null);
socket.addEventListener('message', onEvent);
socket.addEventListener('error', onDiscconect);
socket.addEventListener('close', onDiscconect);
return () => {
socket.removeEventListener('message', onEvent);
socket.removeEventListener('error', onDiscconect);
socket.removeEventListener('close', onDiscconect);
};
} else {
const attemptConnect = () => {
try {
const ws = new WebSocket(websocketAddress);
setSocket(ws);
} catch (error) {
setTimeout(attemptConnect, 5000);
}
return [JSON.parse(event.data), ...state];
});
};
const attemptConnect = () => {
socket.current = new WebSocket('ws://127.0.0.1:13337');
const onError = () => setTimeout(attemptConnect, 1000);
socket.current.addEventListener('error', onError);
socket.current.addEventListener('message', onEvent);
};
attemptConnect();
return () => {
socket.current.removeEventListener('message', onEvent);
socket.current.close();
};
attemptConnect();
}
}, [socket, maxsize, setMessages]);
}, [socket, websocketAddress]);
return (
<ChatLog log={messages} />

View File

@ -1,26 +1,45 @@
import { useRef, useLayoutEffect } from 'react';
import PropTypes from 'prop-types';
import Message from './message';
import Raid from './event-card/raid';
import Subscription from './event-card/sub';
import Follow from './event-card/follow';
import { List } from './chat-log.style';
import { List, Spacer } from './chat-log.style';
const elementMap = {
Message,
Raid,
Follow,
Subscription,
null: Spacer,
};
const selfChats = ['self', 'birbspace'];
function ChatLog({
log,
}) {
// TODO: Should look at full MRO
const renderableEvents = log.filter((event) => Object.keys(elementMap).includes(event.type[0]))
const listRef = useRef();
const renderableEvents = log.map((event) => {
if (!event?.type) {
event = { type: ['null'], data: { }};
}
const bestRenderableInterface = event.type.find((interfaceName) => Object.keys(elementMap).includes(interfaceName));
const renderer = elementMap[bestRenderableInterface];
const deemphasize = !selfChats.includes(event.data.via ?? 'unknown');
return [event, renderer, deemphasize];
}).filter(([event, renderer, deemphasize]) => !!renderer);
useLayoutEffect(() => {
listRef.current.scrollTop = -1;
}, [renderableEvents]);
return (
<List>
{renderableEvents.map((event) => {
const Element = elementMap[event.type[0]];
return (
<Element key={event.data.id} {...event.data} />
);
})}
<List ref={listRef}>
{renderableEvents.map(([event, Element, deemphasize]) => (
<Element key={event.data.id} deemphasize={deemphasize} {...event.data} />
))}
</List>
);
}

View File

@ -2,6 +2,7 @@ import styled from 'styled-components';
export const List = styled.div`
height: 100%;
position: relative;
display: flex;
flex-direction: column-reverse;
align-items: start;
@ -9,4 +10,34 @@ export const List = styled.div`
gap: 16px;
padding: 12px 8px;
overflow: scroll;
scroll-behavior: smooth;
scrollbar-width: none;
&::-webkit-scrollbar {
width: 0px;
background: transparent;
}
font-family: 'Source Code Pro', monospace;
font-size: 24px;
> * {
@keyframes slide {
from {
transform: translateX(-50px);
}
to {
transform: translateX(0);
}
}
animation: 0.3s slide cubic-bezier(.08,.82,.17,1);
}
`;
export const Spacer = styled.div`
min-height: 2em;
`;

View File

@ -0,0 +1,31 @@
import styled, { css } from 'styled-components';
import XTERM_COLORS from '../../../xterm';
export const Root = styled.div`
width: 100%;
position: relative;
background-color: ${XTERM_COLORS[236]};
text-align: center;
`;
export const Foreground = styled.div`
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1;
padding: 12px;
span {
text-shadow: 0 0 4px rgba(0, 0, 0, 60%);
}
`;
export const Background = styled.div`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
`;

View File

@ -0,0 +1,16 @@
import React from 'react';
import { Root, Foreground, Channel } from './follow.style';
const Follow = ({
user_name,
}) => (
<Root>
<Foreground>
<Channel>{user_name}</Channel>
<span>joins the roost~</span>
</Foreground>
</Root>
);
export default Follow;

View File

@ -0,0 +1,8 @@
import styled from 'styled-components';
import XTERM_COLORS from '@/xterm';
export { Root, Background, Foreground } from '../common.styles';
export const Channel = styled.span`
color: ${XTERM_COLORS[6]};
`;

View File

@ -0,0 +1 @@
export { default } from './follow';

View File

@ -0,0 +1 @@
export { default } from './raid';

View File

@ -0,0 +1,26 @@
import React, { useRef, useCallback, useEffect } from 'react';
import { Root, Foreground, Background, Channel } from './raid.style';
import Canvas from '@components/responsive-canvas';
import useZoom from '@/animations/useZoom';
const Raid = ({
from_channel, user_count,
}) => {
const [canvasRef, doAnimation] = useZoom(user_count);
return (
<Root>
<Foreground>
<Channel>{from_channel}</Channel>
<span>soars in with their {user_count}-odd flock!</span>
</Foreground>
<Background>
<Canvas ref={canvasRef} onResize={doAnimation} />
</Background>
</Root>
);
};
export default Raid;

View File

@ -0,0 +1,9 @@
import styled from 'styled-components';
import XTERM_COLORS from '@/xterm';
export { Root, Background, Foreground } from '../common.styles';
export const Channel = styled.span`
color: ${XTERM_COLORS[223]};
font-size: 28px;
`;

View File

@ -0,0 +1 @@
export { default } from './sub';

View File

@ -0,0 +1,47 @@
import React from 'react';
import {
Root, Foreground, Background,
Channel, Streak,
} from './sub.style';
import Canvas from '@components/responsive-canvas';
import useConfetti from '@/animations/useConfetti';
const Sub = ({
user_name, gifted_to, streak,
}) => {
const [canvasRef, doAnimation] = useConfetti(gifted_to?.length ?? 1, 1);
let msg;
if (gifted_to?.length) {
msg = (
<>
<Channel>{user_name}</Channel>
<span>gifted {gifted_to.length} subscriptions!</span>
</>
);
} else {
msg = (
<>
<Channel>{user_name}</Channel>
<span>has subscribed!</span>
{streak > 1 ? (
<Streak>{streak} times and counting!</Streak>
) : null}
</>
);
}
return (
<Root>
<Foreground>{msg}</Foreground>
<Background>
<Canvas ref={canvasRef} onResize={doAnimation} />
</Background>
</Root>
);
};
export default Sub;

View File

@ -0,0 +1,14 @@
import styled from 'styled-components';
import XTERM_COLORS from '@/xterm';
export { Root, Background, Foreground } from '../common.styles';
export const Channel = styled.span`
color: ${XTERM_COLORS[6]};
font-size: 28px;
`;
export const Streak = styled.span`
color: ${XTERM_COLORS[247]};
font-size: 16px;
`;

View File

@ -1,8 +1,12 @@
import { useMemo } from 'react';
import PropTypes from 'prop-types';
import { parse as mfmParse, toString as mfmUnparse } from 'mfm-js';
import { XTERM_COLORS } from '../../../index.style';
import XTERM_COLORS from '../../../xterm';
import { Wrapper, Author } from './message.style';
import { Wrapper, Author, Body, Emote, Attachments } from './message.style';
import * as mfmElements from './mfm';
const stringHashcode = (str) => {
var hash = 0, i, chr;
@ -15,17 +19,62 @@ const stringHashcode = (str) => {
return hash;
}
function Message({
user_id, user_name, user_type, user_color, text,
deemphasize,
}) {
const elForType = {
image: (src) => <img src={src} />,
audio: (src) => <audio src={src} />,
video: (src) => <video src={src} />,
};
const renderMfm = (mfmNode, note) => {
const ElForNode = mfmElements[mfmNode.type];
let node;
if (!ElForNode) {
const text = mfmUnparse(mfmNode);
const TextNode = mfmElements.text;
node = <TextNode note={note} text={text} />;
} else {
const children = mfmNode.children?.map((node) => renderMfm(node, note));
node = <ElForNode note={note} {...mfmNode.props} children={children} />;
}
return node;
};
function Message(note) {
const {
user_id, user_name, user_type, user_color, text, emotes, attachments, deemphasize,
} = note;
const fallbackColor = XTERM_COLORS[(stringHashcode(user_id.toString()) % 144) + 88];
const displayFiles = useMemo(() => {
if (attachments) {
const filtered = attachments
.map(([type, url]) => [type.split('/')[0], url])
.filter(([type, url]) => Object.keys(elForType).includes(type));
return filtered;
}
return null;
}, [attachments]);
const mfmTree = useMemo(() => mfmParse(text), [text]);
const tree = mfmTree.map((node) => renderMfm(node, note));
return (
<Wrapper type={user_type} color={user_color ?? fallbackColor} deemphasize={deemphasize}>
<Author>
[ {user_name} ]
</Author>
{text}
{mfmTree.length !== 0 && (
<Body>
{tree}
</Body>
)}
{!!displayFiles && (
<Attachments>
{displayFiles.map(([type, url]) => elForType[type](url))}
</Attachments>
)}
</Wrapper>
);
}
@ -35,13 +84,20 @@ Message.propTypes = {
user_name: PropTypes.node.isRequired,
user_type: PropTypes.string.isRequired,
user_color: PropTypes.string,
text: PropTypes.node,
emotes: PropTypes.shape({
[PropTypes.string]: PropTypes.string,
}),
deemphasize: PropTypes.bool,
text: PropTypes.node.isRequired,
attachments: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
};
Message.defaultProps = {
text: '',
user_color: undefined,
emotes: {},
deemphasize: false,
attachments: null,
};
export default Message;

View File

@ -1,12 +1,21 @@
import styled, { css } from 'styled-components';
import { toColorString, getContrast, rgbToColorString } from 'polished';
import { XTERM_COLORS } from '../../../index.style';
import XTERM_COLORS from '../../../xterm';
export const Author = styled.div`
width: fit-content;
margin-bottom: 4px;
`;
export const Body = styled.span`
`;
export const Emote = styled.img`
height: 2em;
vertical-align: middle;
`;
export const normalizeColor = (color) => {
if (color === null) {
return 'inherit';
@ -45,8 +54,6 @@ const setColors = ({ type, color, deemphasize }) => {
// console.log(contrasts);
return css`
font-family: 'Source Code Pro', monospace;
font-size: 18px;
color: ${palette.text_fg};
background-color: ${palette.text_bg};
${Author} {
@ -62,19 +69,20 @@ export const Wrapper = styled.div`
flex-direction: column;
width: 100%;
overflow-wrap: break-word;
@keyframes dissapear {
0% {
opacity: 1;
}
15% {
opacity: 1;
}
100% {
opacity: 0;
}
}
animation: 160s dissapear linear;
animation-fill-mode: forwards;
`;
export const Attachments = styled.div`
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px 6px;
padding: 8px;
img {
flex: 1;
min-width: 33%;
max-width: 100%;
max-height: 300px;
object-fit: scale-down;
}
`;

View File

@ -0,0 +1,11 @@
import styled from 'styled-components';
const Embolden = styled.span`
font-weight: bold;
`;
const BoldText = ({ children }) => (
<Embolden>{children}</Embolden>
);
export default BoldText;

View File

@ -0,0 +1,36 @@
import styled from 'styled-components';
import PropTypes from 'prop-types'
import Text from './text';
const Emote = styled.img`
height: 2em;
vertical-align: middle;
`;
const EmojiCode = ({
note, name,
}) => {
if (note.emotes?.[name]) {
return (
<Emote src={note.emotes[name]} title={name} />
);
}
return (
<Text text={`:${name}:`} />
);
};
EmojiCode.propTypes = {
note: PropTypes.shape({
emotes: PropTypes.shape({
[PropTypes.string]: PropTypes.string,
}),
}),
name: PropTypes.string.isRequired,
};
EmojiCode.defaultProps = {
note: { emotes: {} },
};
export default EmojiCode;

View File

@ -0,0 +1,178 @@
import styled, { css, keyframes } from 'styled-components';
const tada = keyframes`
from {
transform: scale3d(1, 1, 1);
}
10%, 20% {
transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
}
30%, 50%, 70%, 90% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}
40%, 60%, 80% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}
to {
transform: scale3d(1, 1, 1);
}
`;
const spin = keyframes`
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
`;
const spinX = keyframes`
0% { transform: perspective(128px) rotateX(0deg); }
100% { transform: perspective(128px) rotateX(360deg); }
`;
const spinY = keyframes`
0% { transform: perspective(128px) rotateY(0deg); }
100% { transform: perspective(128px) rotateY(360deg); }
`;
const jump = keyframes`
0% { transform: translateY(0); }
25% { transform: translateY(-16px); }
50% { transform: translateY(0); }
75% { transform: translateY(-8px); }
100% { transform: translateY(0); }
`;
const bounce = keyframes`
0% { transform: translateY(0) scale(1, 1); }
25% { transform: translateY(-16px) scale(1, 1); }
50% { transform: translateY(0) scale(1, 1); }
75% { transform: translateY(0) scale(1.5, 0.75); }
100% { transform: translateY(0) scale(1, 1); }
`;
const twitch = keyframes`
0% { transform: translate(7px, -2px) }
5% { transform: translate(-3px, 1px) }
10% { transform: translate(-7px, -1px) }
15% { transform: translate(0px, -1px) }
20% { transform: translate(-8px, 6px) }
25% { transform: translate(-4px, -3px) }
30% { transform: translate(-4px, -6px) }
35% { transform: translate(-8px, -8px) }
40% { transform: translate(4px, 6px) }
45% { transform: translate(-3px, 1px) }
50% { transform: translate(2px, -10px) }
55% { transform: translate(-7px, 0px) }
60% { transform: translate(-2px, 4px) }
65% { transform: translate(3px, -8px) }
70% { transform: translate(6px, 7px) }
75% { transform: translate(-7px, -2px) }
80% { transform: translate(-7px, -8px) }
85% { transform: translate(9px, 3px) }
90% { transform: translate(-3px, -2px) }
95% { transform: translate(-10px, 2px) }
100% { transform: translate(-2px, -6px) }
`;
const shake = keyframes`
0% { transform: translate(-3px, -1px) rotate(-8deg) }
5% { transform: translate(0px, -1px) rotate(-10deg) }
10% { transform: translate(1px, -3px) rotate(0deg) }
15% { transform: translate(1px, 1px) rotate(11deg) }
20% { transform: translate(-2px, 1px) rotate(1deg) }
25% { transform: translate(-1px, -2px) rotate(-2deg) }
30% { transform: translate(-1px, 2px) rotate(-3deg) }
35% { transform: translate(2px, 1px) rotate(6deg) }
40% { transform: translate(-2px, -3px) rotate(-9deg) }
45% { transform: translate(0px, -1px) rotate(-12deg) }
50% { transform: translate(1px, 2px) rotate(10deg) }
55% { transform: translate(0px, -3px) rotate(8deg) }
60% { transform: translate(1px, -1px) rotate(8deg) }
65% { transform: translate(0px, -1px) rotate(-7deg) }
70% { transform: translate(-1px, -3px) rotate(6deg) }
75% { transform: translate(0px, -2px) rotate(4deg) }
80% { transform: translate(-2px, -1px) rotate(3deg) }
85% { transform: translate(1px, -3px) rotate(-10deg) }
90% { transform: translate(1px, 0px) rotate(3deg) }
95% { transform: translate(-2px, 0px) rotate(-3deg) }
100% { transform: translate(2px, 1px) rotate(2deg) }
`;
const rubberBand = keyframes`
from { transform: scale3d(1, 1, 1); }
30% { transform: scale3d(1.25, 0.75, 1); }
40% { transform: scale3d(0.75, 1.25, 1); }
50% { transform: scale3d(1.15, 0.85, 1); }
65% { transform: scale3d(0.95, 1.05, 1); }
75% { transform: scale3d(1.05, 0.95, 1); }
to { transform: scale3d(1, 1, 1); }
`;
const rainbow = keyframes`
0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
`;
const validTime = (t, d = null) => {
if (t == null) return d;
return t.match(/^[0-9.]+s$/) ? t : d;
};
// TODO: font, sparkle
const mfmFnStyles = {
tada: css`
font-size: 150%;
animation: ${tada} 1s linear infinite both;
`,
jelly: css`animation: ${rubberBand} ${(props) => validTime(props.speed, '1s')} linear infinite both;`,
twitch: css`animation: ${twitch} ${(props) => validTime(props.speed, '0.5s')} ease infinite;`,
shake: css`animation: ${shake} ${(props) => validTime(props.speed, '0.5s')} ease infinite;`,
spin: (props) => {
let direction;
if (props.left) direction = 'reverse'
else if (props.alternate) direction = 'alternate'
else direction = 'normal';
let anime;
if (props.x) anime = spinX
else if (props.y) anime = spinY
else anime = spin;
return css`
animation: ${anime} ${(props) => validTime(props.speed, '1.5s')} linear infinite;
animation-direction: ${direction};
`;
},
jump: css`animation: ${jump} 0.75s linear infinite;`,
bounce: css`
animation: ${bounce} 0.75s linear infinite;
transform-origin: center bottom;
`,
flip: (props) => {
let transRights;
if (props.h && props.v) transRights = 'scale(-1, -1)';
else if (props.v) transRights = 'scaleY(-1)';
else transRights = 'scaleX(-1)';
return css`
transform: ${transRights};
`;
},
x2: css`font-size: 200%;`,
x3: css`font-size: 400%;`,
x4: css`font-size: 600%;`,
rainbow: css`
color: #f42069;
animation: ${rainbow} 1s linear infinite;`,
rotate: css`
transform: rotate(${(props) => parseInt(props.deg) || '90'}deg);
transform-origin: center center;
`,
};
const MfmFnRunner = styled.span`
> span {
display: inline-block;
${(props) => mfmFnStyles[props.mfmFn]};
}
`;
const MfmFunction = ({ children, name, args }) => (
<MfmFnRunner mfmFn={name} {...args}>
<span>
{children}
</span>
</MfmFnRunner>
);
export default MfmFunction;

View File

@ -0,0 +1,7 @@
export { default as emojiCode } from './emojiCode';
export { default as italic } from './italic';
export { default as bold } from './bold';
export { default as fn } from './fn';
export { default as unicodeEmoji } from './unicodeEmoji';
export { default as url } from './url';
export { default as text } from './text';

View File

@ -0,0 +1,11 @@
import styled from 'styled-components';
const Italic = styled.span`
font-style: italic;
`;
const ItalicText = ({ children }) => (
<Italic>{children}</Italic>
);
export default ItalicText;

View File

@ -0,0 +1,5 @@
const Text = ({ text }) => (
<span>{text}</span>
);
export default Text;

View File

@ -0,0 +1,5 @@
const UnicodeEmoji = ({ emoji }) => (
<span>{emoji}</span>
);
export default UnicodeEmoji;

View File

@ -0,0 +1,5 @@
const Url = ({ url }) => (
<a>{url}</a>
);
export default Url;

View File

@ -0,0 +1,39 @@
/* Copyright 2018 Ross Zurowski
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
// via https://github.com/rosszurowski/react-responsive-canvas/blob/master/src/get-size.js
const parseNumber = prop => parseFloat(prop) || 0;
const getSize = (el: Element) => {
if (el === window || el === document.body) {
return [window.innerWidth, window.innerHeight];
}
let temporary = false;
if (!el.parentNode && document.body) {
temporary = true;
document.body.appendChild(el);
}
const rect = el.getBoundingClientRect();
const styles = getComputedStyle(el);
const height =
(rect.height | 0) +
parseNumber(styles.getPropertyValue('margin-top')) +
parseNumber(styles.getPropertyValue('margin-bottom'));
const width =
(rect.width | 0) +
parseNumber(styles.getPropertyValue('margin-left')) +
parseNumber(styles.getPropertyValue('margin-right'));
if (temporary && document.body) {
document.body.removeChild(el);
}
return [width, height];
};
export default getSize;

View File

@ -0,0 +1 @@
export { default } from './responsive-canvas';

View File

@ -0,0 +1,56 @@
import React, {
useEffect, useState, useCallback, useRef, useImperativeHandle, forwardRef,
} from 'react';
import PropTypes from 'prop-types';
import getSize from './get-size';
const ResponsiveCanvas = forwardRef(({ scale, onResize }, ref) => {
const [width, setWidth] = useState(null);
const [height, setHeight] = useState(null);
const canvasRef = useRef();
useImperativeHandle(ref, () => canvasRef.current);
const handleResize = useCallback(() => {
const parent = canvasRef.current.parentElement;
if (!parent) {
return;
}
const [width, height] = getSize(parent);
setWidth(width);
setHeight(height);
}, [canvasRef]);
useEffect(() => {
window.addEventListener('resize', handleResize, false);
handleResize();
return () => window.removeEventListener('resize', handleResize, false);
}, [handleResize]);
useEffect(() => {
if (onResize && width !== null && height !== null) onResize();
}, [width, height, onResize]);
return (
<canvas
ref={canvasRef}
width={width * scale}
height={height * scale}
style={{ width, height }}
/>
);
});
ResponsiveCanvas.propTypes = {
onResize: PropTypes.func,
scale: PropTypes.number,
};
const defaultScale = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
ResponsiveCanvas.defaultProps = {
onResize: undefined,
scale: defaultScale,
};
export default ResponsiveCanvas;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
src/hooks/index.js Normal file
View File

@ -0,0 +1 @@
export { default as useQuery } from './useQuery';

5
src/hooks/useQuery.js Normal file
View File

@ -0,0 +1,5 @@
import { useLocation } from 'react-router-dom';
export default function useQuery() {
return new URLSearchParams(useLocation().search);
}

View File

@ -1,5 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from "react-router-dom";
import App from './components/app/app';
import { GlobalStyle } from './index.style';
@ -7,7 +8,9 @@ import { GlobalStyle } from './index.style';
ReactDOM.render(
<React.StrictMode>
<GlobalStyle />
<App />
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);

View File

@ -1,12 +1,12 @@
import css, { createGlobalStyle } from 'styled-components';
import { normalize, rgb } from 'polished';
import scpRegular from './fonts/SourceCodePro-Regular.ttf';
import scpLight from './fonts/SourceCodePro-Light.ttf';
import scpBold from './fonts/SourceCodePro-Bold.ttf';
import scpItalic from './fonts/SourceCodePro-Italic.ttf';
import scpLightItalic from './fonts/SourceCodePro-LightItalic.ttf';
import scpBoldItalic from './fonts/SourceCodePro-BoldItalic.ttf';
import scpRegular from './fonts/SourceCodePro-Regular.woff2';
import scpLight from './fonts/SourceCodePro-Light.woff2';
import scpBold from './fonts/SourceCodePro-Bold.woff2';
import scpItalic from './fonts/SourceCodePro-Italic.woff2';
import scpLightItalic from './fonts/SourceCodePro-LightItalic.woff2';
import scpBoldItalic from './fonts/SourceCodePro-BoldItalic.woff2';
export const GlobalStyle = createGlobalStyle`
${normalize()};
@ -14,35 +14,35 @@ export const GlobalStyle = createGlobalStyle`
@font-face {
font-family: "Source Code Pro";
font-weight: normal;
src: url(${scpRegular}) format("truetype");
src: url(${scpRegular}) format("woff2");
}
@font-face {
font-family: "Source Code Pro";
font-weight: 200;
src: url(${scpLight}) format("truetype");
src: url(${scpLight}) format("woff2");
}
@font-face {
font-family: "Source Code Pro";
font-weight: bold;
src: url(${scpBold}) format("truetype");
src: url(${scpBold}) format("woff2");
}
@font-face {
font-family: "Source Code Pro";
font-weight: normal;
font-style: italic;
src: url(${scpItalic}) format("truetype");
src: url(${scpItalic}) format("woff2");
}
@font-face {
font-family: "Source Code Pro";
font-weight: 200;
font-style: italic;
src: url(${scpLightItalic}) format("truetype");
src: url(${scpLightItalic}) format("woff2");
}
@font-face {
font-family: "Source Code Pro";
font-weight: bold;
font-style: italic;
src: url(${scpBoldItalic}) format("truetype");
src: url(${scpBoldItalic}) format("woff2");
}
* {
@ -62,262 +62,3 @@ export const GlobalStyle = createGlobalStyle`
overflow: none;
}
`;
export const XTERM_COLORS = [
rgb(0, 0, 0), // #000000
rgb(128, 0, 0), // #800000
rgb(0, 128, 0), // #008000
rgb(128, 128, 0), // #808000
rgb(0, 0, 128), // #000080
rgb(128, 0, 128), // #800080
rgb(0, 128, 128), // #008080
rgb(192, 192, 192), // #c0c0c0
rgb(128, 128, 128), // #808080
rgb(255, 0, 0), // #ff0000
rgb(0, 255, 0), // #00ff00
rgb(255, 255, 0), // #ffff00
rgb(0, 0, 255), // #0000ff
rgb(255, 0, 255), // #ff00ff
rgb(0, 255, 255), // #00ffff
rgb(255, 255, 255), // #ffffff
rgb(0, 0, 0), // #000000
rgb(0, 0, 95), // #00005f
rgb(0, 0, 135), // #000087
rgb(0, 0, 175), // #0000af
rgb(0, 0, 215), // #0000d7
rgb(0, 0, 255), // #0000ff
rgb(0, 95, 0), // #005f00
rgb(0, 95, 95), // #005f5f
rgb(0, 95, 135), // #005f87
rgb(0, 95, 175), // #005faf
rgb(0, 95, 215), // #005fd7
rgb(0, 95, 255), // #005fff
rgb(0, 135, 0), // #008700
rgb(0, 135, 95), // #00875f
rgb(0, 135, 135), // #008787
rgb(0, 135, 175), // #0087af
rgb(0, 135, 215), // #0087d7
rgb(0, 135, 255), // #0087ff
rgb(0, 175, 0), // #00af00
rgb(0, 175, 95), // #00af5f
rgb(0, 175, 135), // #00af87
rgb(0, 175, 175), // #00afaf
rgb(0, 175, 215), // #00afd7
rgb(0, 175, 255), // #00afff
rgb(0, 215, 0), // #00d700
rgb(0, 215, 95), // #00d75f
rgb(0, 215, 135), // #00d787
rgb(0, 215, 175), // #00d7af
rgb(0, 215, 215), // #00d7d7
rgb(0, 215, 255), // #00d7ff
rgb(0, 255, 0), // #00ff00
rgb(0, 255, 95), // #00ff5f
rgb(0, 255, 135), // #00ff87
rgb(0, 255, 175), // #00ffaf
rgb(0, 255, 215), // #00ffd7
rgb(0, 255, 255), // #00ffff
rgb(95, 0, 0), // #5f0000
rgb(95, 0, 95), // #5f005f
rgb(95, 0, 135), // #5f0087
rgb(95, 0, 175), // #5f00af
rgb(95, 0, 215), // #5f00d7
rgb(95, 0, 255), // #5f00ff
rgb(95, 95, 0), // #5f5f00
rgb(95, 95, 95), // #5f5f5f
rgb(95, 95, 135), // #5f5f87
rgb(95, 95, 175), // #5f5faf
rgb(95, 95, 215), // #5f5fd7
rgb(95, 95, 255), // #5f5fff
rgb(95, 135, 0), // #5f8700
rgb(95, 135, 95), // #5f875f
rgb(95, 135, 135), // #5f8787
rgb(95, 135, 175), // #5f87af
rgb(95, 135, 215), // #5f87d7
rgb(95, 135, 255), // #5f87ff
rgb(95, 175, 0), // #5faf00
rgb(95, 175, 95), // #5faf5f
rgb(95, 175, 135), // #5faf87
rgb(95, 175, 175), // #5fafaf
rgb(95, 175, 215), // #5fafd7
rgb(95, 175, 255), // #5fafff
rgb(95, 215, 0), // #5fd700
rgb(95, 215, 95), // #5fd75f
rgb(95, 215, 135), // #5fd787
rgb(95, 215, 175), // #5fd7af
rgb(95, 215, 215), // #5fd7d7
rgb(95, 215, 255), // #5fd7ff
rgb(95, 255, 0), // #5fff00
rgb(95, 255, 95), // #5fff5f
rgb(95, 255, 135), // #5fff87
rgb(95, 255, 175), // #5fffaf
rgb(95, 255, 215), // #5fffd7
rgb(95, 255, 255), // #5fffff
rgb(135, 0, 0), // #870000
rgb(135, 0, 95), // #87005f
rgb(135, 0, 135), // #870087
rgb(135, 0, 175), // #8700af
rgb(135, 0, 215), // #8700d7
rgb(135, 0, 255), // #8700ff
rgb(135, 95, 0), // #875f00
rgb(135, 95, 95), // #875f5f
rgb(135, 95, 135), // #875f87
rgb(135, 95, 175), // #875faf
rgb(135, 95, 215), // #875fd7
rgb(135, 95, 255), // #875fff
rgb(135, 135, 0), // #878700
rgb(135, 135, 95), // #87875f
rgb(135, 135, 135), // #878787
rgb(135, 135, 175), // #8787af
rgb(135, 135, 215), // #8787d7
rgb(135, 135, 255), // #8787ff
rgb(135, 175, 0), // #87af00
rgb(135, 175, 95), // #87af5f
rgb(135, 175, 135), // #87af87
rgb(135, 175, 175), // #87afaf
rgb(135, 175, 215), // #87afd7
rgb(135, 175, 255), // #87afff
rgb(135, 215, 0), // #87d700
rgb(135, 215, 95), // #87d75f
rgb(135, 215, 135), // #87d787
rgb(135, 215, 175), // #87d7af
rgb(135, 215, 215), // #87d7d7
rgb(135, 215, 255), // #87d7ff
rgb(135, 255, 0), // #87ff00
rgb(135, 255, 95), // #87ff5f
rgb(135, 255, 135), // #87ff87
rgb(135, 255, 175), // #87ffaf
rgb(135, 255, 215), // #87ffd7
rgb(135, 255, 255), // #87ffff
rgb(175, 0, 0), // #af0000
rgb(175, 0, 95), // #af005f
rgb(175, 0, 135), // #af0087
rgb(175, 0, 175), // #af00af
rgb(175, 0, 215), // #af00d7
rgb(175, 0, 255), // #af00ff
rgb(175, 95, 0), // #af5f00
rgb(175, 95, 95), // #af5f5f
rgb(175, 95, 135), // #af5f87
rgb(175, 95, 175), // #af5faf
rgb(175, 95, 215), // #af5fd7
rgb(175, 95, 255), // #af5fff
rgb(175, 135, 0), // #af8700
rgb(175, 135, 95), // #af875f
rgb(175, 135, 135), // #af8787
rgb(175, 135, 175), // #af87af
rgb(175, 135, 215), // #af87d7
rgb(175, 135, 255), // #af87ff
rgb(175, 175, 0), // #afaf00
rgb(175, 175, 95), // #afaf5f
rgb(175, 175, 135), // #afaf87
rgb(175, 175, 175), // #afafaf
rgb(175, 175, 215), // #afafd7
rgb(175, 175, 255), // #afafff
rgb(175, 215, 0), // #afd700
rgb(175, 215, 95), // #afd75f
rgb(175, 215, 135), // #afd787
rgb(175, 215, 175), // #afd7af
rgb(175, 215, 215), // #afd7d7
rgb(175, 215, 255), // #afd7ff
rgb(175, 255, 0), // #afff00
rgb(175, 255, 95), // #afff5f
rgb(175, 255, 135), // #afff87
rgb(175, 255, 175), // #afffaf
rgb(175, 255, 215), // #afffd7
rgb(175, 255, 255), // #afffff
rgb(215, 0, 0), // #d70000
rgb(215, 0, 95), // #d7005f
rgb(215, 0, 135), // #d70087
rgb(215, 0, 175), // #d700af
rgb(215, 0, 215), // #d700d7
rgb(215, 0, 255), // #d700ff
rgb(215, 95, 0), // #d75f00
rgb(215, 95, 95), // #d75f5f
rgb(215, 95, 135), // #d75f87
rgb(215, 95, 175), // #d75faf
rgb(215, 95, 215), // #d75fd7
rgb(215, 95, 255), // #d75fff
rgb(215, 135, 0), // #d78700
rgb(215, 135, 95), // #d7875f
rgb(215, 135, 135), // #d78787
rgb(215, 135, 175), // #d787af
rgb(215, 135, 215), // #d787d7
rgb(215, 135, 255), // #d787ff
rgb(215, 175, 0), // #d7af00
rgb(215, 175, 95), // #d7af5f
rgb(215, 175, 135), // #d7af87
rgb(215, 175, 175), // #d7afaf
rgb(215, 175, 215), // #d7afd7
rgb(215, 175, 255), // #d7afff
rgb(215, 215, 0), // #d7d700
rgb(215, 215, 95), // #d7d75f
rgb(215, 215, 135), // #d7d787
rgb(215, 215, 175), // #d7d7af
rgb(215, 215, 215), // #d7d7d7
rgb(215, 215, 255), // #d7d7ff
rgb(215, 255, 0), // #d7ff00
rgb(215, 255, 95), // #d7ff5f
rgb(215, 255, 135), // #d7ff87
rgb(215, 255, 175), // #d7ffaf
rgb(215, 255, 215), // #d7ffd7
rgb(215, 255, 255), // #d7ffff
rgb(255, 0, 0), // #ff0000
rgb(255, 0, 95), // #ff005f
rgb(255, 0, 135), // #ff0087
rgb(255, 0, 175), // #ff00af
rgb(255, 0, 215), // #ff00d7
rgb(255, 0, 255), // #ff00ff
rgb(255, 95, 0), // #ff5f00
rgb(255, 95, 95), // #ff5f5f
rgb(255, 95, 135), // #ff5f87
rgb(255, 95, 175), // #ff5faf
rgb(255, 95, 215), // #ff5fd7
rgb(255, 95, 255), // #ff5fff
rgb(255, 135, 0), // #ff8700
rgb(255, 135, 95), // #ff875f
rgb(255, 135, 135), // #ff8787
rgb(255, 135, 175), // #ff87af
rgb(255, 135, 215), // #ff87d7
rgb(255, 135, 255), // #ff87ff
rgb(255, 175, 0), // #ffaf00
rgb(255, 175, 95), // #ffaf5f
rgb(255, 175, 135), // #ffaf87
rgb(255, 175, 175), // #ffafaf
rgb(255, 175, 215), // #ffafd7
rgb(255, 175, 255), // #ffafff
rgb(255, 215, 0), // #ffd700
rgb(255, 215, 95), // #ffd75f
rgb(255, 215, 135), // #ffd787
rgb(255, 215, 175), // #ffd7af
rgb(255, 215, 215), // #ffd7d7
rgb(255, 215, 255), // #ffd7ff
rgb(255, 255, 0), // #ffff00
rgb(255, 255, 95), // #ffff5f
rgb(255, 255, 135), // #ffff87
rgb(255, 255, 175), // #ffffaf
rgb(255, 255, 215), // #ffffd7
rgb(255, 255, 255), // #ffffff
rgb(8, 8, 8), // #080808
rgb(18, 18, 18), // #121212
rgb(28, 28, 28), // #1c1c1c
rgb(38, 38, 38), // #262626
rgb(48, 48, 48), // #303030
rgb(58, 58, 58), // #3a3a3a
rgb(68, 68, 68), // #444444
rgb(78, 78, 78), // #4e4e4e
rgb(88, 88, 88), // #585858
rgb(98, 98, 98), // #626262
rgb(108, 108, 108), // #6c6c6c
rgb(118, 118, 118), // #767676
rgb(128, 128, 128), // #808080
rgb(138, 138, 138), // #8a8a8a
rgb(148, 148, 148), // #949494
rgb(158, 158, 158), // #9e9e9e
rgb(168, 168, 168), // #a8a8a8
rgb(178, 178, 178), // #b2b2b2
rgb(188, 188, 188), // #bcbcbc
rgb(198, 198, 198), // #c6c6c6
rgb(208, 208, 208), // #d0d0d0
rgb(218, 218, 218), // #dadada
rgb(228, 228, 228), // #e4e4e4
rgb(238, 238, 238), // #eeeeee
];

257
src/xterm.js Normal file
View File

@ -0,0 +1,257 @@
import { rgb } from 'polished'; export default [ rgb(0, 0, 0), // #000000
rgb(128, 0, 0), // #800000
rgb(0, 128, 0), // #008000
rgb(128, 128, 0), // #808000
rgb(0, 0, 128), // #000080
rgb(128, 0, 128), // #800080
rgb(0, 128, 128), // #008080
rgb(192, 192, 192), // #c0c0c0
rgb(128, 128, 128), // #808080
rgb(255, 0, 0), // #ff0000
rgb(0, 255, 0), // #00ff00
rgb(255, 255, 0), // #ffff00
rgb(0, 0, 255), // #0000ff
rgb(255, 0, 255), // #ff00ff
rgb(0, 255, 255), // #00ffff
rgb(255, 255, 255), // #ffffff
rgb(0, 0, 0), // #000000
rgb(0, 0, 95), // #00005f
rgb(0, 0, 135), // #000087
rgb(0, 0, 175), // #0000af
rgb(0, 0, 215), // #0000d7
rgb(0, 0, 255), // #0000ff
rgb(0, 95, 0), // #005f00
rgb(0, 95, 95), // #005f5f
rgb(0, 95, 135), // #005f87
rgb(0, 95, 175), // #005faf
rgb(0, 95, 215), // #005fd7
rgb(0, 95, 255), // #005fff
rgb(0, 135, 0), // #008700
rgb(0, 135, 95), // #00875f
rgb(0, 135, 135), // #008787
rgb(0, 135, 175), // #0087af
rgb(0, 135, 215), // #0087d7
rgb(0, 135, 255), // #0087ff
rgb(0, 175, 0), // #00af00
rgb(0, 175, 95), // #00af5f
rgb(0, 175, 135), // #00af87
rgb(0, 175, 175), // #00afaf
rgb(0, 175, 215), // #00afd7
rgb(0, 175, 255), // #00afff
rgb(0, 215, 0), // #00d700
rgb(0, 215, 95), // #00d75f
rgb(0, 215, 135), // #00d787
rgb(0, 215, 175), // #00d7af
rgb(0, 215, 215), // #00d7d7
rgb(0, 215, 255), // #00d7ff
rgb(0, 255, 0), // #00ff00
rgb(0, 255, 95), // #00ff5f
rgb(0, 255, 135), // #00ff87
rgb(0, 255, 175), // #00ffaf
rgb(0, 255, 215), // #00ffd7
rgb(0, 255, 255), // #00ffff
rgb(95, 0, 0), // #5f0000
rgb(95, 0, 95), // #5f005f
rgb(95, 0, 135), // #5f0087
rgb(95, 0, 175), // #5f00af
rgb(95, 0, 215), // #5f00d7
rgb(95, 0, 255), // #5f00ff
rgb(95, 95, 0), // #5f5f00
rgb(95, 95, 95), // #5f5f5f
rgb(95, 95, 135), // #5f5f87
rgb(95, 95, 175), // #5f5faf
rgb(95, 95, 215), // #5f5fd7
rgb(95, 95, 255), // #5f5fff
rgb(95, 135, 0), // #5f8700
rgb(95, 135, 95), // #5f875f
rgb(95, 135, 135), // #5f8787
rgb(95, 135, 175), // #5f87af
rgb(95, 135, 215), // #5f87d7
rgb(95, 135, 255), // #5f87ff
rgb(95, 175, 0), // #5faf00
rgb(95, 175, 95), // #5faf5f
rgb(95, 175, 135), // #5faf87
rgb(95, 175, 175), // #5fafaf
rgb(95, 175, 215), // #5fafd7
rgb(95, 175, 255), // #5fafff
rgb(95, 215, 0), // #5fd700
rgb(95, 215, 95), // #5fd75f
rgb(95, 215, 135), // #5fd787
rgb(95, 215, 175), // #5fd7af
rgb(95, 215, 215), // #5fd7d7
rgb(95, 215, 255), // #5fd7ff
rgb(95, 255, 0), // #5fff00
rgb(95, 255, 95), // #5fff5f
rgb(95, 255, 135), // #5fff87
rgb(95, 255, 175), // #5fffaf
rgb(95, 255, 215), // #5fffd7
rgb(95, 255, 255), // #5fffff
rgb(135, 0, 0), // #870000
rgb(135, 0, 95), // #87005f
rgb(135, 0, 135), // #870087
rgb(135, 0, 175), // #8700af
rgb(135, 0, 215), // #8700d7
rgb(135, 0, 255), // #8700ff
rgb(135, 95, 0), // #875f00
rgb(135, 95, 95), // #875f5f
rgb(135, 95, 135), // #875f87
rgb(135, 95, 175), // #875faf
rgb(135, 95, 215), // #875fd7
rgb(135, 95, 255), // #875fff
rgb(135, 135, 0), // #878700
rgb(135, 135, 95), // #87875f
rgb(135, 135, 135), // #878787
rgb(135, 135, 175), // #8787af
rgb(135, 135, 215), // #8787d7
rgb(135, 135, 255), // #8787ff
rgb(135, 175, 0), // #87af00
rgb(135, 175, 95), // #87af5f
rgb(135, 175, 135), // #87af87
rgb(135, 175, 175), // #87afaf
rgb(135, 175, 215), // #87afd7
rgb(135, 175, 255), // #87afff
rgb(135, 215, 0), // #87d700
rgb(135, 215, 95), // #87d75f
rgb(135, 215, 135), // #87d787
rgb(135, 215, 175), // #87d7af
rgb(135, 215, 215), // #87d7d7
rgb(135, 215, 255), // #87d7ff
rgb(135, 255, 0), // #87ff00
rgb(135, 255, 95), // #87ff5f
rgb(135, 255, 135), // #87ff87
rgb(135, 255, 175), // #87ffaf
rgb(135, 255, 215), // #87ffd7
rgb(135, 255, 255), // #87ffff
rgb(175, 0, 0), // #af0000
rgb(175, 0, 95), // #af005f
rgb(175, 0, 135), // #af0087
rgb(175, 0, 175), // #af00af
rgb(175, 0, 215), // #af00d7
rgb(175, 0, 255), // #af00ff
rgb(175, 95, 0), // #af5f00
rgb(175, 95, 95), // #af5f5f
rgb(175, 95, 135), // #af5f87
rgb(175, 95, 175), // #af5faf
rgb(175, 95, 215), // #af5fd7
rgb(175, 95, 255), // #af5fff
rgb(175, 135, 0), // #af8700
rgb(175, 135, 95), // #af875f
rgb(175, 135, 135), // #af8787
rgb(175, 135, 175), // #af87af
rgb(175, 135, 215), // #af87d7
rgb(175, 135, 255), // #af87ff
rgb(175, 175, 0), // #afaf00
rgb(175, 175, 95), // #afaf5f
rgb(175, 175, 135), // #afaf87
rgb(175, 175, 175), // #afafaf
rgb(175, 175, 215), // #afafd7
rgb(175, 175, 255), // #afafff
rgb(175, 215, 0), // #afd700
rgb(175, 215, 95), // #afd75f
rgb(175, 215, 135), // #afd787
rgb(175, 215, 175), // #afd7af
rgb(175, 215, 215), // #afd7d7
rgb(175, 215, 255), // #afd7ff
rgb(175, 255, 0), // #afff00
rgb(175, 255, 95), // #afff5f
rgb(175, 255, 135), // #afff87
rgb(175, 255, 175), // #afffaf
rgb(175, 255, 215), // #afffd7
rgb(175, 255, 255), // #afffff
rgb(215, 0, 0), // #d70000
rgb(215, 0, 95), // #d7005f
rgb(215, 0, 135), // #d70087
rgb(215, 0, 175), // #d700af
rgb(215, 0, 215), // #d700d7
rgb(215, 0, 255), // #d700ff
rgb(215, 95, 0), // #d75f00
rgb(215, 95, 95), // #d75f5f
rgb(215, 95, 135), // #d75f87
rgb(215, 95, 175), // #d75faf
rgb(215, 95, 215), // #d75fd7
rgb(215, 95, 255), // #d75fff
rgb(215, 135, 0), // #d78700
rgb(215, 135, 95), // #d7875f
rgb(215, 135, 135), // #d78787
rgb(215, 135, 175), // #d787af
rgb(215, 135, 215), // #d787d7
rgb(215, 135, 255), // #d787ff
rgb(215, 175, 0), // #d7af00
rgb(215, 175, 95), // #d7af5f
rgb(215, 175, 135), // #d7af87
rgb(215, 175, 175), // #d7afaf
rgb(215, 175, 215), // #d7afd7
rgb(215, 175, 255), // #d7afff
rgb(215, 215, 0), // #d7d700
rgb(215, 215, 95), // #d7d75f
rgb(215, 215, 135), // #d7d787
rgb(215, 215, 175), // #d7d7af
rgb(215, 215, 215), // #d7d7d7
rgb(215, 215, 255), // #d7d7ff
rgb(215, 255, 0), // #d7ff00
rgb(215, 255, 95), // #d7ff5f
rgb(215, 255, 135), // #d7ff87
rgb(215, 255, 175), // #d7ffaf
rgb(215, 255, 215), // #d7ffd7
rgb(215, 255, 255), // #d7ffff
rgb(255, 0, 0), // #ff0000
rgb(255, 0, 95), // #ff005f
rgb(255, 0, 135), // #ff0087
rgb(255, 0, 175), // #ff00af
rgb(255, 0, 215), // #ff00d7
rgb(255, 0, 255), // #ff00ff
rgb(255, 95, 0), // #ff5f00
rgb(255, 95, 95), // #ff5f5f
rgb(255, 95, 135), // #ff5f87
rgb(255, 95, 175), // #ff5faf
rgb(255, 95, 215), // #ff5fd7
rgb(255, 95, 255), // #ff5fff
rgb(255, 135, 0), // #ff8700
rgb(255, 135, 95), // #ff875f
rgb(255, 135, 135), // #ff8787
rgb(255, 135, 175), // #ff87af
rgb(255, 135, 215), // #ff87d7
rgb(255, 135, 255), // #ff87ff
rgb(255, 175, 0), // #ffaf00
rgb(255, 175, 95), // #ffaf5f
rgb(255, 175, 135), // #ffaf87
rgb(255, 175, 175), // #ffafaf
rgb(255, 175, 215), // #ffafd7
rgb(255, 175, 255), // #ffafff
rgb(255, 215, 0), // #ffd700
rgb(255, 215, 95), // #ffd75f
rgb(255, 215, 135), // #ffd787
rgb(255, 215, 175), // #ffd7af
rgb(255, 215, 215), // #ffd7d7
rgb(255, 215, 255), // #ffd7ff
rgb(255, 255, 0), // #ffff00
rgb(255, 255, 95), // #ffff5f
rgb(255, 255, 135), // #ffff87
rgb(255, 255, 175), // #ffffaf
rgb(255, 255, 215), // #ffffd7
rgb(255, 255, 255), // #ffffff
rgb(8, 8, 8), // #080808
rgb(18, 18, 18), // #121212
rgb(28, 28, 28), // #1c1c1c
rgb(38, 38, 38), // #262626
rgb(48, 48, 48), // #303030
rgb(58, 58, 58), // #3a3a3a
rgb(68, 68, 68), // #444444
rgb(78, 78, 78), // #4e4e4e
rgb(88, 88, 88), // #585858
rgb(98, 98, 98), // #626262
rgb(108, 108, 108), // #6c6c6c
rgb(118, 118, 118), // #767676
rgb(128, 128, 128), // #808080
rgb(138, 138, 138), // #8a8a8a
rgb(148, 148, 148), // #949494
rgb(158, 158, 158), // #9e9e9e
rgb(168, 168, 168), // #a8a8a8
rgb(178, 178, 178), // #b2b2b2
rgb(188, 188, 188), // #bcbcbc
rgb(198, 198, 198), // #c6c6c6
rgb(208, 208, 208), // #d0d0d0
rgb(218, 218, 218), // #dadada
rgb(228, 228, 228), // #e4e4e4
rgb(238, 238, 238), // #eeeeee
];