├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── README.md
├── next-sitemap.config.js
├── next.config.js
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── prettier
├── public
├── favicon.ico
├── favicon
│ ├── apple-touch-icon.png
│ ├── favicon-48x48.png
│ ├── favicon.ico
│ ├── favicon.svg
│ ├── site.webmanifest
│ ├── web-app-manifest-192x192.png
│ └── web-app-manifest-512x512.png
├── fonts
│ └── inter-var-latin.woff2
├── images
│ ├── logo.png
│ ├── logo192.png
│ └── logo256.png
└── svg
│ ├── Logo.svg
│ └── Vercel.svg
├── src
├── app
│ ├── _components
│ │ ├── Nav.tsx
│ │ └── SensorDetails.tsx
│ ├── layout.tsx
│ ├── page.tsx
│ └── v3
│ │ └── page.tsx
├── constant
│ ├── config.ts
│ └── env.ts
├── lib
│ ├── deob
│ │ ├── clean.ts
│ │ └── index.ts
│ └── parse
│ │ ├── v2
│ │ ├── dec.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ │ └── v3
│ │ ├── dec.ts
│ │ ├── detailed.ts
│ │ ├── index.ts
│ │ └── utils.ts
└── styles
│ └── globals.css
├── tailwind.config.ts
├── tsconfig.json
└── vercel.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | node: true,
6 | },
7 | plugins: ['@typescript-eslint', 'simple-import-sort', 'unused-imports'],
8 | extends: [
9 | 'eslint:recommended',
10 | 'next',
11 | 'next/core-web-vitals',
12 | 'plugin:@typescript-eslint/recommended',
13 | 'prettier',
14 | ],
15 | rules: {
16 | 'no-unused-vars': 'off',
17 | 'no-console': 'warn',
18 | '@typescript-eslint/explicit-module-boundary-types': 'off',
19 | 'react/no-unescaped-entities': 'off',
20 |
21 | 'react/display-name': 'off',
22 | 'react/jsx-curly-brace-presence': [
23 | 'warn',
24 | { props: 'never', children: 'never' },
25 | ],
26 |
27 | //#region //*=========== Unused Import ===========
28 | '@typescript-eslint/no-unused-vars': 'off',
29 | 'unused-imports/no-unused-imports': 'warn',
30 | 'unused-imports/no-unused-vars': [
31 | 'warn',
32 | {
33 | vars: 'all',
34 | varsIgnorePattern: '^_',
35 | args: 'after-used',
36 | argsIgnorePattern: '^_',
37 | },
38 | ],
39 | //#endregion //*======== Unused Import ===========
40 |
41 | //#region //*=========== Import Sort ===========
42 | 'simple-import-sort/exports': 'warn',
43 | 'simple-import-sort/imports': [
44 | 'warn',
45 | {
46 | groups: [
47 | // ext library & side effect imports
48 | ['^@?\\w', '^\\u0000'],
49 | // {s}css files
50 | ['^.+\\.s?css$'],
51 | // Lib and hooks
52 | ['^@/lib', '^@/hooks'],
53 | // static data
54 | ['^@/data'],
55 | // components
56 | ['^@/_components', '^@/container'],
57 | // zustand store
58 | ['^@/store'],
59 | // Other imports
60 | ['^@/'],
61 | // relative paths up until 3 level
62 | [
63 | '^\\./?$',
64 | '^\\.(?!/?$)',
65 | '^\\.\\./?$',
66 | '^\\.\\.(?!/?$)',
67 | '^\\.\\./\\.\\./?$',
68 | '^\\.\\./\\.\\.(?!/?$)',
69 | '^\\.\\./\\.\\./\\.\\./?$',
70 | '^\\.\\./\\.\\./\\.\\.(?!/?$)',
71 | ],
72 | ['^@/types'],
73 | // other that didnt fit in
74 | ['^'],
75 | ],
76 | },
77 | ],
78 | //#endregion //*======== Import Sort ===========
79 | },
80 | globals: {
81 | React: true,
82 | JSX: true,
83 | },
84 | };
85 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env
30 | .env*.local
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
39 | # next-sitemap
40 | robots.txt
41 | sitemap.xml
42 | sitemap-*.xml
43 |
44 | /ignore
45 |
46 | /public/collector.js
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | enable-pre-post-scripts=true
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20.10.0
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## [https://abck.dev/](https://abck.dev/)
2 |
--------------------------------------------------------------------------------
/next-sitemap.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('next-sitemap').IConfig}
3 | * @see https://github.com/iamvishnusankar/next-sitemap#readme
4 | */
5 | module.exports = {
6 | siteUrl: 'https://abck.dev',
7 | generateRobotsTxt: true,
8 | robotsTxtOptions: {
9 | policies: [{ userAgent: '*', allow: '/' }],
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | eslint: {
4 | dirs: ['src'],
5 | },
6 |
7 | reactStrictMode: true,
8 | swcMinify: true,
9 |
10 | // Uncoment to add domain whitelist
11 | // images: {
12 | // remotePatterns: [
13 | // {
14 | // protocol: 'https',
15 | // hostname: 'res.cloudinary.com',
16 | // },
17 | // ]
18 | // },
19 |
20 | webpack(config) {
21 | // Grab the existing rule that handles SVG imports
22 | const fileLoaderRule = config.module.rules.find((rule) =>
23 | rule.test?.test?.('.svg')
24 | );
25 |
26 | config.module.rules.push(
27 | // Reapply the existing rule, but only for svg imports ending in ?url
28 | {
29 | ...fileLoaderRule,
30 | test: /\.svg$/i,
31 | resourceQuery: /url/, // *.svg?url
32 | },
33 | // Convert all other *.svg imports to React components
34 | {
35 | test: /\.svg$/i,
36 | issuer: { not: /\.(css|scss|sass)$/ },
37 | resourceQuery: { not: /url/ }, // exclude if *.svg?url
38 | loader: '@svgr/webpack',
39 | options: {
40 | dimensions: false,
41 | titleProp: true,
42 | },
43 | }
44 | );
45 |
46 | // Modify the file loader rule to ignore *.svg, since we have it handled now.
47 | fileLoaderRule.exclude = /\.svg$/i;
48 |
49 | return config;
50 | },
51 | };
52 |
53 | module.exports = nextConfig;
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ts-nextjs-tailwind-starter",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "lint:strict": "eslint --max-warnings=0 src",
11 | "typecheck": "tsc --noEmit --incremental false",
12 | "postbuild": "next-sitemap --config next-sitemap.config.js"
13 | },
14 | "dependencies": {
15 | "next": "^14.2.13",
16 | "react": "^18.2.0"
17 | },
18 | "devDependencies": {
19 | "@svgr/webpack": "^8.1.0",
20 | "@tailwindcss/forms": "^0.5.9",
21 | "@tailwindcss/typography": "^0.5.15",
22 | "@types/react": "^18.3.4",
23 | "@typescript-eslint/eslint-plugin": "^5.62.0",
24 | "@typescript-eslint/parser": "^5.62.0",
25 | "autoprefixer": "^10.4.20",
26 | "eslint": "^8.57.1",
27 | "eslint-config-next": "^14.2.13",
28 | "eslint-config-prettier": "^8.10.0",
29 | "eslint-plugin-simple-import-sort": "^7.0.0",
30 | "eslint-plugin-unused-imports": "^2.0.0",
31 | "next-router-mock": "^0.9.0",
32 | "next-sitemap": "^2.5.28",
33 | "postcss": "^8.4.45",
34 | "prettier": "^2.8.8",
35 | "prettier-plugin-tailwindcss": "^0.5.14",
36 | "tailwindcss": "^3.4.12",
37 | "typescript": "^4.9.5"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/prettier:
--------------------------------------------------------------------------------
1 | /** @type {import("prettier").Config} */
2 |
3 | module.exports = {
4 | singleQuote: true,
5 | tabWidth: 2,
6 | semi: true,
7 | trailingComma: 'all',
8 | arrowParens: 'always',
9 | parser: 'typescript',
10 | plugins: ['prettier-plugin-tailwindcss'],
11 | };
12 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon/favicon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/favicon/favicon-48x48.png
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/favicon/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "abck.dev",
3 | "short_name": "abck",
4 | "icons": [
5 | {
6 | "src": "/favicon/web-app-manifest-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png",
9 | "purpose": "maskable"
10 | },
11 | {
12 | "src": "/favicon/web-app-manifest-512x512.png",
13 | "sizes": "512x512",
14 | "type": "image/png",
15 | "purpose": "maskable"
16 | }
17 | ],
18 | "theme_color": "#000000",
19 | "background_color": "#000000",
20 | "display": "standalone"
21 | }
--------------------------------------------------------------------------------
/public/favicon/web-app-manifest-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/favicon/web-app-manifest-192x192.png
--------------------------------------------------------------------------------
/public/favicon/web-app-manifest-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/favicon/web-app-manifest-512x512.png
--------------------------------------------------------------------------------
/public/fonts/inter-var-latin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/fonts/inter-var-latin.woff2
--------------------------------------------------------------------------------
/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/images/logo.png
--------------------------------------------------------------------------------
/public/images/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/images/logo192.png
--------------------------------------------------------------------------------
/public/images/logo256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zedd3v/abck-dev/bc6264ba0a7a2e2ceeb2593dd638b50076bf8036/public/images/logo256.png
--------------------------------------------------------------------------------
/public/svg/Logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/svg/Vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/_components/Nav.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Link from 'next/link';
3 |
4 | import Logo from '~/images/logo.png';
5 |
6 | const Nav = () => {
7 | return (
8 |
9 |
10 |
14 | V2
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 | V3
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | export default Nav;
33 |
--------------------------------------------------------------------------------
/src/app/_components/SensorDetails.tsx:
--------------------------------------------------------------------------------
1 | export interface SensorDetailsProps {
2 | key: string;
3 | value: string;
4 | mismatch?: boolean;
5 | inverted?: boolean;
6 | }
7 |
8 | const SensorDetails = ({ key, value, mismatch = false, inverted = false }: SensorDetailsProps) => {
9 | return (
10 |
21 | );
22 | };
23 |
24 | export default SensorDetails;
25 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import * as React from 'react';
3 |
4 | import '@/styles/globals.css';
5 |
6 | import { siteConfig } from '@/constant/config';
7 |
8 | export const metadata: Metadata = {
9 | metadataBase: new URL(siteConfig.url),
10 | title: {
11 | default: siteConfig.title,
12 | template: `%s | ${siteConfig.title}`,
13 | },
14 | description: siteConfig.description,
15 | robots: { index: true, follow: true },
16 | icons: {
17 | icon: '/favicon/favicon.ico',
18 | shortcut: '/favicon/favicon-16x16.png',
19 | apple: '/favicon/apple-touch-icon.png',
20 | },
21 | manifest: `/favicon/site.webmanifest`,
22 | openGraph: {
23 | url: siteConfig.url,
24 | title: siteConfig.title,
25 | description: siteConfig.description,
26 | siteName: siteConfig.title,
27 | images: [`${siteConfig.url}/images/logo.png`],
28 | type: 'website',
29 | locale: 'en_US',
30 | },
31 | twitter: {
32 | card: 'summary_large_image',
33 | title: siteConfig.title,
34 | description: siteConfig.description,
35 | images: [`${siteConfig.url}/images/logo.png`],
36 | },
37 | };
38 |
39 | export default function RootLayout({ children }: { children: React.ReactNode }) {
40 | return (
41 |
42 | {children}
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, { useEffect, useState } from 'react';
4 |
5 | import { ParsedSensor, parseSensor } from '@/lib/parse/v2';
6 |
7 | import Nav from '@/app/_components/Nav';
8 |
9 | export default function HomePage() {
10 | const [sensorL, setSensorL] = useState('');
11 | const [parsedSensorL, setParsedSensorL] = useState({});
12 |
13 | useEffect(() => {
14 | if (sensorL) {
15 | setParsedSensorL(parseSensor(sensorL, false));
16 | }
17 | }, [sensorL]);
18 |
19 | const [sensorR, setSensorR] = React.useState('');
20 | const [parsedSensorR, setParsedSensorR] = useState({});
21 |
22 | useEffect(() => {
23 | if (sensorR) {
24 | setParsedSensorR(parseSensor(sensorR, false));
25 | }
26 | }, [sensorR]);
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
48 |
49 |
64 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/src/app/v3/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, { useEffect, useState } from 'react';
4 |
5 | import { ParseResult, parseSensor } from '@/lib/parse/v3';
6 |
7 | import Nav from '@/app/_components/Nav';
8 | import SensorDetails, { SensorDetailsProps } from '@/app/_components/SensorDetails';
9 |
10 | export default function HomePage() {
11 | const [encodeKeyL, setEncodeKeyL] = useState(0);
12 | const [sensorL, setSensorL] = useState('');
13 | const [parsedSensorL, setParsedSensorL] = useState({} as ParseResult);
14 |
15 | useEffect(() => {
16 | if (encodeKeyL && sensorL) {
17 | setParsedSensorL(parseSensor(sensorL, encodeKeyL, true));
18 | } else {
19 | setParsedSensorL({ sensor: '' });
20 | }
21 | }, [encodeKeyL, sensorL]);
22 |
23 | const [encodeKeyR, setEncodeKeyR] = useState(0);
24 | const [sensorR, setSensorR] = React.useState('');
25 | const [parsedSensorR, setParsedSensorR] = useState({} as ParseResult);
26 |
27 | useEffect(() => {
28 | if (encodeKeyR && sensorR) {
29 | setParsedSensorR(parseSensor(sensorR, encodeKeyR, true));
30 | } else {
31 | setParsedSensorR({ sensor: '' });
32 | }
33 | }, [encodeKeyR, sensorR]);
34 |
35 | return (
36 |
37 |
38 |
116 |
117 | );
118 | }
119 |
--------------------------------------------------------------------------------
/src/constant/config.ts:
--------------------------------------------------------------------------------
1 | export const siteConfig = {
2 | title: 'abck.dev',
3 | description:
4 | 'abck.dev',
5 | url: 'https://abck.dev',
6 | };
7 |
--------------------------------------------------------------------------------
/src/constant/env.ts:
--------------------------------------------------------------------------------
1 | export const isProd = process.env.NODE_ENV === 'production';
2 | export const isLocal = process.env.NODE_ENV === 'development';
3 |
--------------------------------------------------------------------------------
/src/lib/deob/clean.ts:
--------------------------------------------------------------------------------
1 | const cleanPropCalls = (script: string): string => {
2 | let cleaned = script.toString();
3 |
4 | // clean prop calls
5 | // ["property"] => .property
6 | for (let tries = 0; tries < 5; tries += 1) {
7 | const propMatches = [
8 | ...cleaned.matchAll(/([^ =:])\[(?:\x60|"|')([a-zA-Z]\w*)(?:\x60|"|')\]/gim),
9 | ];
10 | if (propMatches.length > 0) {
11 | // eslint-disable-next-line no-loop-func
12 | propMatches.forEach((m) => {
13 | cleaned = cleaned.replace(m[0], `${m[1]}.${m[2]}`);
14 | });
15 | }
16 | }
17 |
18 | return cleaned;
19 | };
20 |
21 | const clean = (script: string): string => {
22 | return cleanPropCalls(script);
23 | };
24 |
25 | export default clean;
26 |
--------------------------------------------------------------------------------
/src/lib/deob/index.ts:
--------------------------------------------------------------------------------
1 | import clean from './clean';
2 |
3 | const err = new Error('something failed. oopsie');
4 |
5 | // browser behaviour made it dirty
6 | const getDeobVals = (deobScript: string, deobArr: string[]) => {
7 | let evalScript = `${deobScript};return{`;
8 |
9 | deobArr.forEach((deobCall) => {
10 | const q = deobCall.includes("'") ? '"' : "'";
11 |
12 | evalScript += `${q}${deobCall}${q}:${decodeURIComponent(deobCall)},`;
13 | });
14 |
15 | // eslint-disable-next-line no-new-func
16 | return new Function(`${evalScript}};`)();
17 | };
18 |
19 | // waa waa, waaa, youre not using ast! fiend!
20 | const deobfuscate = (script: string): string => {
21 | const originalScript = script;
22 |
23 | // grab main case
24 | const mainAkam =
25 | /(do{switch\(\w+\){|break;)case\s?(\w{2}):{\s?((?:\w{2}(?:\+|-)?=\w{2};)?\w{2}\s?=\s?\(function\(\w{2}\)\{return\s?\w{2}\.apply.*?)}\s?break;case\s?(\w{2}):{/i.exec(
26 | script
27 | );
28 |
29 | if (!mainAkam || mainAkam.length < 3) throw err;
30 |
31 | // remove the main execution case
32 | let deobScript = script.replace(
33 | mainAkam[0],
34 | `${mainAkam[1]}case ${mainAkam[2]}:{return;}break;case ${mainAkam[4]}:{`
35 | );
36 |
37 | // remove the top parent function return
38 | const topFuncReturn = /return\s?(\w{2}\.call\(this,\s?\w{2}\))/i.exec(deobScript);
39 | if (!topFuncReturn || topFuncReturn.length < 1) throw err;
40 |
41 | deobScript = deobScript.replace(topFuncReturn[0], topFuncReturn[1]).slice(12, -5);
42 |
43 | let cleanedScript = `// zed_pinocchio;\n${originalScript}`;
44 |
45 | // get deobbed vals and replace
46 | const deobMatches = [
47 | ...cleanedScript.matchAll(
48 | /\w{2}\.\w{2}\((?:\w{2,3},\s?\w{2,3}|\w{2,3},\s?\w{2,3},\s?\w{2,3},\s?\w{2,3}|".*?",\s?\w{2,3}|\w{2,3},\s?\w{2,3},\s?\w{2,3})\)/gim
49 | ),
50 | ];
51 | if (deobMatches.length > 0) {
52 | const deobArr = [...new Set(deobMatches.map((m) => encodeURIComponent(m[0])))];
53 | const deobVals = getDeobVals(deobScript, deobArr);
54 |
55 | // deob everything (not everything, sowwy i lied)
56 | deobMatches.forEach((m) => {
57 | cleanedScript = cleanedScript.replace(m[0], `\`${deobVals[encodeURIComponent(m[0])]}\``);
58 | });
59 | }
60 |
61 | return clean(cleanedScript);
62 | };
63 |
64 | export default deobfuscate;
65 |
--------------------------------------------------------------------------------
/src/lib/parse/v2/dec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-bitwise */
2 |
3 | const decodeArr = [
4 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
5 | -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, -1, 2, 3, 4, 5, -1, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
6 | 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
7 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 58, 59, 60, 61, 62, 63,
8 | 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
9 | 88, 89, 90, 91,
10 | ];
11 |
12 | const chars =
13 | ' !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~';
14 |
15 | export const firstDec = (sensor: string, bmSzFirstComp: number): string => {
16 | let dec = '';
17 |
18 | let bmSzFC = bmSzFirstComp;
19 |
20 | for (let i = 0; i < sensor.length; i += 1) {
21 | const shifted = (bmSzFC >> 8) & 65535;
22 | bmSzFC *= 65793;
23 | bmSzFC &= 4294967295;
24 | bmSzFC += 4282663;
25 | bmSzFC &= 8388607;
26 |
27 | // snippet taken from github.com/SteakEnthusiast
28 | let newIndex = decodeArr[sensor.charCodeAt(i)];
29 | newIndex -= shifted % chars.length;
30 | if (newIndex < 0) {
31 | newIndex += chars.length;
32 | }
33 |
34 | dec += chars[newIndex];
35 | }
36 |
37 | return dec;
38 | };
39 |
40 | export const secondDec = (payload: string, bmSzSecondComp: number): string => {
41 | const pcs = payload.split(',');
42 | const mixingIndexes = [];
43 | let bmSzSC = bmSzSecondComp;
44 |
45 | for (let i = 0; i < pcs.length; i += 1) {
46 | const x = ((bmSzSC >> 8) & 65535) % pcs.length;
47 | bmSzSC *= 65793;
48 | bmSzSC &= 4294967295;
49 | bmSzSC += 4282663;
50 | bmSzSC &= 8388607;
51 |
52 | const y = ((bmSzSC >> 8) & 65535) % pcs.length;
53 | bmSzSC *= 65793;
54 | bmSzSC &= 4294967295;
55 | bmSzSC += 4282663;
56 | bmSzSC &= 8388607;
57 |
58 | mixingIndexes.push([x, y]);
59 | }
60 |
61 | for (let i = mixingIndexes.length - 1; i >= 0; i -= 1) {
62 | const [x, y] = mixingIndexes[i];
63 |
64 | const pc = pcs[x];
65 | pcs[x] = pcs[y];
66 | pcs[y] = pc;
67 | }
68 |
69 | return pcs.join(',');
70 | };
71 |
--------------------------------------------------------------------------------
/src/lib/parse/v2/index.ts:
--------------------------------------------------------------------------------
1 | import { getSeperator, isJSON, isBool, RawSensorJson } from './utils';
2 | import { secondDec, firstDec } from './dec';
3 |
4 | interface Divider {
5 | identifier: string;
6 | endIdentifier?: string;
7 | name: string;
8 | reverse?: boolean; // incase data is behind the divider - necessary for first children
9 | children?: Divider[];
10 | last?: boolean;
11 | }
12 |
13 | export interface ParsedSensor {
14 | [name: string]: string;
15 | }
16 |
17 | const dividers: Divider[] = [
18 | { identifier: '-100', name: 'deviceData' },
19 | { identifier: '-105', name: 'informinfo' },
20 | { identifier: '-108', name: 'bmak.kact' },
21 | { identifier: '-101', name: 'windowEvents' },
22 | { identifier: '-110', name: 'bmak.mact' },
23 | { identifier: '-117', name: 'bmak.tact' },
24 | { identifier: '-109', name: 'bmak.dmact' },
25 | { identifier: '-102', name: 'informinfo' },
26 | { identifier: '-111', name: 'bmak.doact' },
27 | { identifier: '-114', name: 'bmak.pact' },
28 | { identifier: '-103', name: 'bmak.vcact' },
29 | { identifier: '-106', name: 'bmak.aj_type + bmak.aj_indx' },
30 | { identifier: '-115', name: 'bunch of stuff' },
31 | { identifier: '-112', name: 'document.URL' },
32 | { identifier: '-119', name: 'bmak.mr' },
33 | { identifier: '-122', name: 'sed' },
34 | { identifier: '-123', name: 'mn_r part 1' },
35 | { identifier: '-124', name: 'mn_r part 2' },
36 | { identifier: '-126', name: 'mn_r part 3' },
37 | { identifier: '-127', name: 'bmak.nav_perm' },
38 | { identifier: '-128', name: 'one28' },
39 | { identifier: '-131', name: 'one31' },
40 | { identifier: '-132', name: 'one32' },
41 | { identifier: '-133', name: 'one33' },
42 | { identifier: '-70', name: 'bmak.fpcf.fpValStr' },
43 | { identifier: '-80', name: 'fpValHash' },
44 | { identifier: '-90', name: 'dynamicFuncRes' },
45 | { identifier: '-116', name: 'bmak.o9' },
46 | { identifier: '-129', name: 'some fingerprint stuff', last: true },
47 | ];
48 |
49 | function recursiveSplitByDivider(
50 | sensor: string,
51 | detailed: boolean,
52 | divider: Divider,
53 | seperator: string
54 | ): ParsedSensor | null {
55 | const { name, children, reverse, endIdentifier, last, identifier } = divider;
56 |
57 | let finalParsedSensor: ParsedSensor = {};
58 |
59 | let firstSplit = [];
60 |
61 | let secondSplit = [];
62 |
63 | if (!reverse) {
64 | firstSplit = sensor.split(divider.identifier + seperator);
65 |
66 | if (firstSplit.length < 2) return null;
67 |
68 | if (endIdentifier) {
69 | if (firstSplit[1].includes(endIdentifier)) {
70 | secondSplit = firstSplit[1].split(endIdentifier);
71 | } else {
72 | secondSplit = firstSplit;
73 | }
74 | } else {
75 | secondSplit = firstSplit[1].split(seperator);
76 | }
77 | } else {
78 | firstSplit = sensor.split(endIdentifier || divider.identifier);
79 | if (firstSplit.length < 2) return null;
80 |
81 | secondSplit = firstSplit;
82 | }
83 |
84 | if (!last && secondSplit.length < 2) return null;
85 |
86 | let value = secondSplit[0];
87 |
88 | if (detailed && children && children.length > 0) {
89 | children.forEach((child) => {
90 | if (value.length < 3) return;
91 | const childParsedSensor = recursiveSplitByDivider(value, detailed, child, seperator);
92 | if (childParsedSensor) {
93 | finalParsedSensor = {
94 | ...finalParsedSensor,
95 | ...childParsedSensor,
96 | };
97 | const replaceValue = `${childParsedSensor[child.name]}${child.endIdentifier ? child.endIdentifier : ''}`;
98 | value = value.replace(replaceValue, '');
99 | } else {
100 | let splitReplace = '';
101 | if (child.endIdentifier) splitReplace = child.endIdentifier;
102 | [, value] = value.split(splitReplace);
103 | }
104 | });
105 | } else {
106 | finalParsedSensor = {
107 | ...finalParsedSensor,
108 | [name]: value,
109 | };
110 | }
111 |
112 | if (identifier === '-100' || identifier === '-115') {
113 | const val = finalParsedSensor[name];
114 | if (val.includes(':;:')) {
115 | console.log('\n', JSON.stringify(val.split(',;,').sort()));
116 | } else {
117 | console.log('\n', JSON.stringify(val.split(',')));
118 | }
119 | }
120 |
121 | return finalParsedSensor;
122 | }
123 |
124 | const parseBmSzComps = (sensor: string): number[] => {
125 | return sensor
126 | .slice(2)
127 | .split(';')
128 | .slice(0, 2)
129 | .map((n) => Number(n));
130 | };
131 |
132 | export function parseSensor(sensor: string, detailed: boolean): ParsedSensor {
133 | try {
134 | let rawSensor = sensor.toString();
135 |
136 | const jsonCheck = isJSON(sensor);
137 | if (!isBool(jsonCheck) && 'sensor_data' in (jsonCheck as RawSensorJson)) {
138 | rawSensor = (jsonCheck as RawSensorJson).sensor_data;
139 | }
140 |
141 | const bmSzComps = parseBmSzComps(rawSensor);
142 |
143 | // console.log('\nbmSzComps', bmSzComps);
144 |
145 | const dirty = rawSensor.split(';').slice(4).join(';');
146 |
147 | // console.log('\ndirty', dirty);
148 |
149 | const unshuffled = firstDec(dirty, bmSzComps[0]);
150 |
151 | // console.log('\nunshuffled', unshuffled);
152 |
153 | const decoded = secondDec(unshuffled, bmSzComps[1]);
154 |
155 | // console.log('\ndecoded', decoded);
156 |
157 | const seperator = getSeperator(decoded);
158 |
159 | // console.log('\nseperator', JSON.stringify(decoded.split(seperator)));
160 |
161 | let parsedSensor: ParsedSensor = {};
162 |
163 | dividers.forEach((d) => {
164 | const values = recursiveSplitByDivider(decoded, detailed, d, seperator);
165 | parsedSensor = { ...parsedSensor, ...values };
166 | });
167 |
168 | return parsedSensor;
169 | } catch (e) {
170 | console.error(e);
171 | return {};
172 | }
173 | }
174 |
175 | export const CamelCaseToSentenceCase = (camelCaseString: string): string => {
176 | const result = camelCaseString.replace(/([A-Z])/g, ' $1');
177 | return `${result.charAt(0).toUpperCase()}${result.slice(1)}`;
178 | };
179 |
--------------------------------------------------------------------------------
/src/lib/parse/v2/utils.ts:
--------------------------------------------------------------------------------
1 | export const getSeperator = (cleanSensor: string): string => {
2 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3 | // @ts-ignore: Object is possibly 'null'.
4 | return /2([a-zA-Z0-9!@#%&\-_=;:<>,~]+)2\1(?:7a7)/i.exec(cleanSensor)[1];
5 | };
6 |
7 | export const isBool = (v: unknown): boolean => typeof v === 'boolean';
8 |
9 | export interface RawSensorJson {
10 | // eslint-disable-next-line camelcase
11 | sensor_data: string;
12 | }
13 |
14 | export const isJSON = (str: string): boolean | RawSensorJson => {
15 | try {
16 | const o = JSON.parse(str);
17 |
18 | if (o && typeof o === 'object') {
19 | return o;
20 | }
21 | } catch {
22 | // i want func to not crush, but don't care about the message
23 | }
24 |
25 | return false;
26 | };
27 |
--------------------------------------------------------------------------------
/src/lib/parse/v3/dec.ts:
--------------------------------------------------------------------------------
1 | const decodeArr = [
2 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3 | -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, -1, 2, 3, 4, 5, -1, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
4 | 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
5 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 58, 59, 60, 61, 62, 63,
6 | 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
7 | 88, 89, 90, 91,
8 | ];
9 |
10 | const chars =
11 | ' !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~';
12 |
13 | const decodeChar = (char: string, shiftedComp: number) => {
14 | let pos = chars.indexOf(char);
15 | if (pos < 92) pos += 92;
16 |
17 | pos -= shiftedComp % 92;
18 | if (pos > 92) pos -= 92;
19 |
20 | return String.fromCharCode(decodeArr.indexOf(pos));
21 | };
22 |
23 | export const firstDec = (sensor: string, bmSzFirstComp: number): string => {
24 | const dec = [];
25 |
26 | let bmSzFC = bmSzFirstComp;
27 |
28 | for (let i = 0; i < sensor.length; i += 1) {
29 | const shifted = (bmSzFC >> 8) & 65535;
30 |
31 | bmSzFC *= 65793;
32 | bmSzFC &= 4294967295;
33 | bmSzFC += 4282663;
34 | bmSzFC &= 8388607;
35 |
36 | const charCode = sensor.charCodeAt(i);
37 | if (charCode < 32 || charCode === 39 || charCode === 34 || charCode === 92) {
38 | dec[i] = sensor[i];
39 | } else {
40 | dec[i] = decodeChar(sensor[i], shifted);
41 | }
42 | }
43 |
44 | return dec.join('');
45 | };
46 |
47 | export const secondDec = (payload: string, bmSzSecondComp: number): string => {
48 | console.log('\nbmSzSecondComp', bmSzSecondComp); // 7229381
49 |
50 | const pcs = payload.split(':');
51 | const mixingIndexes = [];
52 | let bmSzSC = bmSzSecondComp;
53 |
54 | for (let i = 0; i < pcs.length; i += 1) {
55 | const x = ((bmSzSC >> 8) & 65535) % pcs.length;
56 | bmSzSC *= 65793;
57 | bmSzSC &= 4294967295;
58 | bmSzSC += 4282663;
59 | bmSzSC &= 8388607;
60 |
61 | const y = ((bmSzSC >> 8) & 65535) % pcs.length;
62 | bmSzSC *= 65793;
63 | bmSzSC &= 4294967295;
64 | bmSzSC += 4282663;
65 | bmSzSC &= 8388607;
66 |
67 | mixingIndexes.push([x, y]);
68 | }
69 |
70 | for (let i = mixingIndexes.length - 1; i >= 0; i -= 1) {
71 | const [x, y] = mixingIndexes[i];
72 |
73 | const pc = pcs[x];
74 | pcs[x] = pcs[y];
75 | pcs[y] = pc;
76 | }
77 |
78 | return pcs.join(':');
79 | };
80 |
--------------------------------------------------------------------------------
/src/lib/parse/v3/detailed.ts:
--------------------------------------------------------------------------------
1 | interface Dividers {
2 | [key: string]: {
3 | divider: string;
4 | keys: {
5 | index: number;
6 | key: string;
7 | }[];
8 | };
9 | }
10 |
11 | const dividers: Dividers = {
12 | fpt: {
13 | divider: ';',
14 | keys: [
15 | { index: 3, key: 'plugins' },
16 | { index: 4, key: 'sessionStorage' },
17 | { index: 5, key: 'localStorage' },
18 | { index: 6, key: 'indexedDb' },
19 | { index: 7, key: 'timezoneOffset' },
20 | { index: 8, key: 'rtc' },
21 | { index: 9, key: 'colorDepth' },
22 | { index: 10, key: 'pixelDepth' },
23 | { index: 11, key: 'cookieEnabled' },
24 | { index: 12, key: 'javaEnabled' },
25 | { index: 13, key: 'dnt' },
26 | ],
27 | },
28 | xof: {
29 | divider: ',',
30 | keys: [
31 | { index: 0, key: 'hardwareConcurrency' },
32 | { index: 1, key: 'pluginArray.length' },
33 | { index: 2, key: 'window.chrome' },
34 | { index: 3, key: 'bool(plugins.length)' },
35 | { index: 4, key: 'deviceMemory' },
36 | ],
37 | },
38 | wsl: {
39 | divider: ',',
40 | keys: [
41 | { index: 0, key: 'jusHeapSizeLimit' },
42 | { index: 1, key: 'totalJSHeapSize' },
43 | { index: 2, key: 'usedJSHeapSize' },
44 | { index: 3, key: 'connection.rtt' },
45 | { index: 4, key: 'voicesLength' },
46 | { index: 5, key: 'n.pls[0][0].enabledPlugin' },
47 | { index: 6, key: 'n.pls.refresh' },
48 | { index: 7, key: 'n.pls.item(4294967296)' },
49 | { index: 8, key: 'File.prototype.path' },
50 | { index: 9, key: 'sharedArrayBuffer' },
51 | ],
52 | },
53 | };
54 |
55 | const makePrintVal = (v: string | number): string => {
56 | return typeof v === 'string' ? `"${v}"` : String(v);
57 | };
58 |
59 | const parseSensorDetails = (decoded: string): { [key: string]: string } => {
60 | const json = JSON.parse(decoded);
61 |
62 | const details: { [key: string]: string } = {};
63 |
64 | for (const key in json) {
65 | const val = json[key];
66 |
67 | if (Array.isArray(val)) {
68 | // sort alphabetically
69 | const sorted = [];
70 |
71 | for (const idx in val) {
72 | for (const subKey in val[idx]) {
73 | sorted.push(subKey);
74 | }
75 | }
76 |
77 | sorted.sort();
78 |
79 | for (const k of sorted) {
80 | for (const idx in val) {
81 | if (k in val[idx]) {
82 | details[`${key}.${k}`] = makePrintVal(val[idx][k]);
83 |
84 | if (k in dividers) {
85 | const { divider, keys } = dividers[k];
86 |
87 | val[idx][k].split(divider).forEach((v: string, i: number) => {
88 | const dividerKey = keys.find((k) => k.index === i);
89 | if (dividerKey) {
90 | details[`${key}.${k}.${dividerKey.key}`] = makePrintVal(v);
91 | }
92 | });
93 | }
94 | }
95 | }
96 | }
97 | } else {
98 | details[key] = makePrintVal(val);
99 |
100 | if (key in dividers) {
101 | const { divider, keys } = dividers[key];
102 |
103 | val.split(divider).forEach((v: string, i: number) => {
104 | const dividerKey = keys.find((k) => k.index === i);
105 | if (dividerKey) {
106 | details[`${key}.${dividerKey.key}`] = makePrintVal(v);
107 | }
108 | });
109 | }
110 | }
111 | }
112 |
113 | return details;
114 | };
115 |
116 | export default parseSensorDetails;
117 |
--------------------------------------------------------------------------------
/src/lib/parse/v3/index.ts:
--------------------------------------------------------------------------------
1 | import { firstDec, secondDec } from './dec';
2 | import parseSensorDetails from './detailed';
3 | import { isBool, isJSON, RawSensorJson } from './utils';
4 |
5 | const parseFirstBmSzComp = (sensor: string): number => {
6 | return Number(sensor.split(';')[4]);
7 | };
8 |
9 | export interface ParseResult {
10 | sensor: string;
11 | detailed?: {
12 | [key: string]: string;
13 | };
14 | }
15 |
16 | export function parseSensor(sensor: string, encodeKey: number, detailed: boolean): ParseResult {
17 | try {
18 | let rawSensor = sensor.toString();
19 |
20 | const jsonCheck = isJSON(sensor);
21 | if (!isBool(jsonCheck) && 'sensor_data' in (jsonCheck as RawSensorJson)) {
22 | rawSensor = (jsonCheck as RawSensorJson).sensor_data;
23 | }
24 |
25 | const bmSzComps = [parseFirstBmSzComp(rawSensor), encodeKey];
26 |
27 | // console.log('\nbmSzComps', bmSzComps);
28 |
29 | const dirty = rawSensor.split(';').slice(7).join(';');
30 |
31 | // console.log('\ndirty', dirty);
32 |
33 | const unshuffled = firstDec(dirty, bmSzComps[0]);
34 |
35 | // console.log('\nunshuffled', unshuffled);
36 |
37 | const decoded = secondDec(unshuffled, bmSzComps[1]);
38 |
39 | // console.log('\ndecoded', decoded);
40 |
41 | const parsedSensor = JSON.stringify(JSON.parse(decoded), null, '\t');
42 |
43 | if (!detailed) {
44 | return { sensor: parsedSensor };
45 | }
46 |
47 | return { sensor: parsedSensor, detailed: parseSensorDetails(decoded) };
48 | } catch (e) {
49 | console.error(e);
50 | return { sensor: '' };
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/lib/parse/v3/utils.ts:
--------------------------------------------------------------------------------
1 | export const getSeperator = (cleanSensor: string): string => {
2 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3 | // @ts-ignore: Object is possibly 'null'.
4 | return /2([a-zA-Z0-9!@#%&\-_=;:<>,~]+)2\1(?:7a7)/i.exec(cleanSensor)[1];
5 | };
6 |
7 | export const isBool = (v: unknown): boolean => typeof v === 'boolean';
8 |
9 | export interface RawSensorJson {
10 | // eslint-disable-next-line camelcase
11 | sensor_data: string;
12 | }
13 |
14 | export const isJSON = (str: string): boolean | RawSensorJson => {
15 | try {
16 | const o = JSON.parse(str);
17 |
18 | if (o && typeof o === 'object') {
19 | return o;
20 | }
21 | } catch {
22 | // i want func to not crush, but don't care about the message
23 | }
24 |
25 | return false;
26 | };
27 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | /* remove default css from buttons */
6 | button::-moz-focus-inner,
7 | input[type="button"]::-moz-focus-inner,
8 | input[type="submit"]::-moz-focus-inner,
9 | input[type="reset"]::-moz-focus-inner {
10 | padding: 0 !important;
11 | border: 0 none !important;
12 | }
13 |
14 | /* Hide scrollbar for Chrome, Safari and Opera */
15 | .no-scrollbar::-webkit-scrollbar {
16 | display: none;
17 | }
18 | /* Hide scrollbar for IE, Edge and Firefox */
19 | .no-scrollbar {
20 | -ms-overflow-style: none; /* IE and Edge */
21 | scrollbar-width: none; /* Firefox */
22 | }
23 |
24 |
25 | .scrollbar-thin::-webkit-scrollbar {
26 | width: 5px; /* width of the entire scrollbar */
27 | }
28 |
29 | .scrollbar-thin::-webkit-scrollbar-track {
30 | background: transparent; /* color of the tracking area */
31 | }
32 |
33 | .scrollbar-thin::-webkit-scrollbar-thumb {
34 | background-color: #9ca3af7d; /* color of the scroll thumb */
35 | border-radius: 4px; /* roundness of the scroll thumb */
36 | border: 2px solid transparent; /* creates padding around scroll thumb */
37 | }
38 |
39 |
40 | @-moz-document url-prefix() {
41 | .scrollbar-thin {
42 | scrollbar-width: thin; /* Firefox */
43 | scrollbar-color: #9ca3af7d transparent; /* Firefox */
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss';
2 | import defaultTheme from 'tailwindcss/defaultTheme';
3 |
4 | /** @type {import('tailwindcss').Config} */
5 | // precompile dynamic color classes
6 | import colors from './node_modules/tailwindcss/colors';
7 | const safelist = [];
8 | const extendedColors: { [key: string]: string | { [key: number]: string } } = {};
9 | const colorValues = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
10 |
11 | for (const key in colors) {
12 | if (
13 | // avoid deprecated color warnings && non custom colors
14 | ![
15 | 'lightBlue',
16 | 'warmGray',
17 | 'trueGray',
18 | 'coolGray',
19 | 'blueGray',
20 | 'inherit',
21 | 'current',
22 | 'transparent',
23 | 'black',
24 | 'white',
25 | ].includes(key)
26 | ) {
27 | extendedColors[key] = colors[key as keyof typeof colors];
28 |
29 | for (const value of colorValues) {
30 | safelist.push(`bg-${key}-${value}`);
31 | safelist.push(`text-${key}-${value}`);
32 | }
33 | }
34 | }
35 |
36 | export default {
37 | content: ['./src/**/*.{js,jsx,ts,tsx}'],
38 | theme: {
39 | extend: {
40 | fontFamily: {
41 | primary: ['Inter', ...defaultTheme.fontFamily.sans],
42 | },
43 | keyframes: {
44 | 'up-down': {
45 | '0%, 100%': { transform: 'translateY(0)' },
46 | '50%': { transform: 'translateY(-10px)' },
47 | },
48 | },
49 | animation: {
50 | 'up-down': 'up-down 2s linear infinite',
51 | },
52 | screens: {
53 | xs: '475px',
54 | },
55 | transitionProperty: {
56 | height: 'height',
57 | 'max-height': 'max-height',
58 | spacing: 'margin, padding',
59 | },
60 | colors: extendedColors,
61 | },
62 | },
63 | safelist,
64 | plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
65 | darkMode: 'class',
66 | } satisfies Config;
67 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "baseUrl": ".",
17 | "paths": {
18 | "@/*": ["./src/*"],
19 | "~/*": ["./public/*"]
20 | },
21 | "incremental": true,
22 | "plugins": [
23 | {
24 | "name": "next"
25 | }
26 | ]
27 | },
28 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
29 | "exclude": ["node_modules"],
30 | "moduleResolution": ["node_modules", ".next", "node"]
31 | }
32 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "headers": [
3 | {
4 | "source": "/fonts/inter-var-latin.woff2",
5 | "headers": [
6 | {
7 | "key": "Cache-Control",
8 | "value": "public, max-age=31536000, immutable"
9 | }
10 | ]
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------