Compare commits
22 Commits
1af139201d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 97296843df | |||
| 1faab39849 | |||
| 0ad0e362d6 | |||
| 7fbf3593fb | |||
| 8830b130ad | |||
| aaecba5642 | |||
| 05c3dcd5b5 | |||
| f678c0fa54 | |||
| ef09b642d6 | |||
| 7852a77a7c | |||
| 582eb68139 | |||
| d0ff245582 | |||
| a109c7115e | |||
| 8a06d7cb93 | |||
| 60eb56da56 | |||
| 0877993b41 | |||
| d404f5daab | |||
| 8e0ae3dd83 | |||
| 85d98a41c1 | |||
| e1fe000608 | |||
| e853268e52 | |||
| 4428cc7e8a |
32
.gitignore
vendored
Normal file → Executable file
32
.gitignore
vendored
Normal file → Executable file
@@ -1,14 +1,24 @@
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
test-results
|
||||
node_modules
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
9
.prettierignore
Executable file
9
.prettierignore
Executable file
@@ -0,0 +1,9 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
bun.lock
|
||||
bun.lockb
|
||||
|
||||
# Miscellaneous
|
||||
/static/
|
||||
16
.prettierrc
Executable file
16
.prettierrc
Executable file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tailwindStylesheet": "./src/app.css"
|
||||
}
|
||||
39
README.md
Normal file → Executable file
39
README.md
Normal file → Executable file
@@ -1,3 +1,38 @@
|
||||
# olszewski.ink
|
||||
# sv
|
||||
|
||||
Repository for vite driven svelte project of a portfolio with The Bash
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```sh
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
|
||||
6
e2e/demo.test.ts
Executable file
6
e2e/demo.test.ts
Executable file
@@ -0,0 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('home page has expected h1', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
});
|
||||
41
eslint.config.js
Executable file
41
eslint.config.js
Executable file
@@ -0,0 +1,41 @@
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import js from '@eslint/js';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import globals from 'globals';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelteConfig from './svelte.config.js';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default defineConfig(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
prettier,
|
||||
...svelte.configs.prettier,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node }
|
||||
},
|
||||
rules: {
|
||||
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
'no-undef': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: ['.svelte'],
|
||||
parser: ts.parser,
|
||||
svelteConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
4878
package-lock.json
generated
Executable file
4878
package-lock.json
generated
Executable file
File diff suppressed because it is too large
Load Diff
49
package.json
Executable file
49
package.json
Executable file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "svelte-portfolio",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test:e2e": "playwright test",
|
||||
"test": "npm run test:e2e && npm run test:unit -- --run",
|
||||
"test:unit": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.4.0",
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@playwright/test": "^1.55.1",
|
||||
"@sveltejs/adapter-auto": "^6.1.0",
|
||||
"@sveltejs/kit": "^2.43.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.0",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@types/node": "^22",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.12.4",
|
||||
"globals": "^16.4.0",
|
||||
"playwright": "^1.55.1",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"svelte": "^5.39.5",
|
||||
"svelte-check": "^4.3.2",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.44.1",
|
||||
"vite": "^7.1.7",
|
||||
"vitest": "^3.2.4",
|
||||
"vitest-browser-svelte": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lucide/svelte": "^0.548.0"
|
||||
}
|
||||
}
|
||||
9
playwright.config.ts
Executable file
9
playwright.config.ts
Executable file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 4173
|
||||
},
|
||||
testDir: 'e2e'
|
||||
});
|
||||
3101
pnpm-lock.yaml
generated
Executable file
3101
pnpm-lock.yaml
generated
Executable file
File diff suppressed because it is too large
Load Diff
50
src/app.css
Executable file
50
src/app.css
Executable file
@@ -0,0 +1,50 @@
|
||||
@import './style/global.css';
|
||||
@import 'tailwindcss';
|
||||
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body,
|
||||
#app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--text);
|
||||
background-color: var(--bg-dark);
|
||||
background-image: radial-gradient(circle at center, var(--bg-light) 1px, transparent 1px);
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
13
src/app.d.ts
vendored
Executable file
13
src/app.d.ts
vendored
Executable file
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
11
src/app.html
Executable file
11
src/app.html
Executable file
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
7
src/demo.spec.ts
Executable file
7
src/demo.spec.ts
Executable file
@@ -0,0 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('sum test', () => {
|
||||
it('adds 1 + 2 to equal 3', () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
});
|
||||
10
src/lib/assets/deFlag.svg
Executable file
10
src/lib/assets/deFlag.svg
Executable file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512" xml:space="preserve">
|
||||
<path style="fill:#464655;" d="M473.655,88.276H38.345C17.167,88.276,0,105.443,0,126.621v73.471h512v-73.471
|
||||
C512,105.443,494.833,88.276,473.655,88.276z"/>
|
||||
<path style="fill:#FFE15A;" d="M0,385.379c0,21.177,17.167,38.345,38.345,38.345h435.31c21.177,0,38.345-17.167,38.345-38.345
|
||||
v-73.471H0V385.379z"/>
|
||||
<rect y="200.09" style="fill:#FF4B55;" width="512" height="111.81"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 668 B |
2
src/lib/assets/enFlag.svg
Executable file
2
src/lib/assets/enFlag.svg
Executable file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--twemoji" preserveAspectRatio="xMidYMid meet"><path fill="#00247D" d="M0 9.059V13h5.628zM4.664 31H13v-5.837zM23 25.164V31h8.335zM0 23v3.941L5.63 23zM31.337 5H23v5.837zM36 26.942V23h-5.631zM36 13V9.059L30.371 13zM13 5H4.664L13 10.837z"></path><path fill="#CF1B2B" d="M25.14 23l9.712 6.801a3.977 3.977 0 0 0 .99-1.749L28.627 23H25.14zM13 23h-2.141l-9.711 6.8c.521.53 1.189.909 1.938 1.085L13 23.943V23zm10-10h2.141l9.711-6.8a3.988 3.988 0 0 0-1.937-1.085L23 12.057V13zm-12.141 0L1.148 6.2a3.994 3.994 0 0 0-.991 1.749L7.372 13h3.487z"></path><path fill="#EEE" d="M36 21H21v10h2v-5.836L31.335 31H32a3.99 3.99 0 0 0 2.852-1.199L25.14 23h3.487l7.215 5.052c.093-.337.158-.686.158-1.052v-.058L30.369 23H36v-2zM0 21v2h5.63L0 26.941V27c0 1.091.439 2.078 1.148 2.8l9.711-6.8H13v.943l-9.914 6.941c.294.07.598.116.914.116h.664L13 25.163V31h2V21H0zM36 9a3.983 3.983 0 0 0-1.148-2.8L25.141 13H23v-.943l9.915-6.942A4.001 4.001 0 0 0 32 5h-.663L23 10.837V5h-2v10h15v-2h-5.629L36 9.059V9zM13 5v5.837L4.664 5H4a3.985 3.985 0 0 0-2.852 1.2l9.711 6.8H7.372L.157 7.949A3.968 3.968 0 0 0 0 9v.059L5.628 13H0v2h15V5h-2z"></path><path fill="#CF1B2B" d="M21 15V5h-6v10H0v6h15v10h6V21h15v-6z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/lib/assets/favicon.svg
Executable file
1
src/lib/assets/favicon.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
10
src/lib/assets/frFlag.svg
Executable file
10
src/lib/assets/frFlag.svg
Executable file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512" xml:space="preserve">
|
||||
<path style="fill:#41479B;" d="M38.345,88.273C17.167,88.273,0,105.44,0,126.618v258.759c0,21.177,17.167,38.345,38.345,38.345
|
||||
h132.322V88.273H38.345z"/>
|
||||
<rect x="170.67" y="88.277" style="fill:#F5F5F5;" width="170.67" height="335.45"/>
|
||||
<path style="fill:#FF4B55;" d="M473.655,88.273H341.333v335.448h132.322c21.177,0,38.345-17.167,38.345-38.345V126.618
|
||||
C512,105.44,494.833,88.273,473.655,88.273z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 695 B |
1804
src/lib/assets/fs/fs.json
Executable file
1804
src/lib/assets/fs/fs.json
Executable file
File diff suppressed because it is too large
Load Diff
1
src/lib/assets/fs/signature
Executable file
1
src/lib/assets/fs/signature
Executable file
@@ -0,0 +1 @@
|
||||
32aac83d-ce2b-4def-977c-dcdcb6f514ef
|
||||
9
src/lib/assets/jaFlag.svg
Executable file
9
src/lib/assets/jaFlag.svg
Executable file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512" xml:space="preserve">
|
||||
<path style="fill:#F5F5F5;" d="M473.655,88.275H38.345C17.167,88.275,0,105.442,0,126.62V385.38
|
||||
c0,21.177,17.167,38.345,38.345,38.345h435.31c21.177,0,38.345-17.167,38.345-38.345V126.62
|
||||
C512,105.442,494.833,88.275,473.655,88.275z"/>
|
||||
<circle style="fill:#FF4B55;" cx="256" cy="255.999" r="97.1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 620 B |
2
src/lib/assets/plFlag.svg
Executable file
2
src/lib/assets/plFlag.svg
Executable file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--twemoji" preserveAspectRatio="xMidYMid meet"><path fill="#EEE" d="M32 5H4a4 4 0 0 0-4 4v9h36V9a4 4 0 0 0-4-4z"></path><path fill="#DC143C" d="M0 27a4 4 0 0 0 4 4h28a4 4 0 0 0 4-4v-9H0v9z"></path></svg>
|
||||
|
After Width: | Height: | Size: 506 B |
BIN
src/lib/assets/quan.ttf
Executable file
BIN
src/lib/assets/quan.ttf
Executable file
Binary file not shown.
1
src/lib/index.ts
Executable file
1
src/lib/index.ts
Executable file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
190
src/lib/stores/bash/bash.ts
Executable file
190
src/lib/stores/bash/bash.ts
Executable file
@@ -0,0 +1,190 @@
|
||||
import { COMMANDS, GROUP, PASSWD, type CommandArgs, type ICommand, type Result } from './static';
|
||||
import { VirtualFS } from './fs';
|
||||
import { Terminal, type PrintData } from '../terminal/terminal';
|
||||
import { Stack } from '../stack';
|
||||
|
||||
export type Permission = {
|
||||
r: boolean;
|
||||
w: boolean;
|
||||
x: boolean;
|
||||
};
|
||||
|
||||
export type BashInitArgs = {
|
||||
stdio?: Terminal;
|
||||
user: User;
|
||||
fs: any;
|
||||
};
|
||||
|
||||
export type TimeStamps = {
|
||||
modified: Date;
|
||||
changed: Date;
|
||||
accessed: Date;
|
||||
};
|
||||
|
||||
// TODO: Finish this
|
||||
// TODO: Change into a type instead of an enum for performance (low priority)
|
||||
export enum ExitCode {
|
||||
SUCCESS = 0,
|
||||
ERROR = 1
|
||||
}
|
||||
|
||||
export type User = {
|
||||
username: string;
|
||||
passwd: string; //HASHED PASSWORD //TODO: Make a formated type
|
||||
readonly uid: number; // Normal user 1000+ System user 1-999 root - 0 //TODO: Make a formated type
|
||||
readonly gid: number; // Primary group | 'Users' 1000 - Others - 1000+ root - 0 //TODO: Make a formated type
|
||||
home: string; //TODO: Make a formated type
|
||||
history: string[];
|
||||
cwd?: number; //TODO: Make a formated type
|
||||
pwd?: number; //TODO: Make a formated type
|
||||
};
|
||||
|
||||
export type Group = {
|
||||
groupname: string;
|
||||
gid: number; // Primary group 'Users' 1000 - Others - 1000+ root - 0
|
||||
members: number[]; //TODO: Make a formated type UID
|
||||
};
|
||||
|
||||
export class Bash {
|
||||
private vfs: VirtualFS;
|
||||
private _passwd: User[];
|
||||
private _instances: Stack<User>;
|
||||
private _group: Group[];
|
||||
private _terminal!: Terminal;
|
||||
private user: User;
|
||||
private readonly _commands: Record<string, ICommand>;
|
||||
|
||||
constructor(args: BashInitArgs) {
|
||||
this.user = args.user;
|
||||
this._commands = COMMANDS;
|
||||
this._passwd = PASSWD;
|
||||
this._group = GROUP;
|
||||
this._terminal = args.stdio!;
|
||||
this._instances = new Stack<User>();
|
||||
|
||||
this.vfs = new VirtualFS({ fs: args.fs, user: args.user });
|
||||
}
|
||||
|
||||
private _appendNewResult(inode: number, output: any, cmd: string) {
|
||||
const data: PrintData = {
|
||||
path: this.vfs.formatPath(this.vfs.getPathByInode(inode)),
|
||||
output: output,
|
||||
cmd: cmd
|
||||
};
|
||||
console.log(data);
|
||||
this._terminal.PrintOutput(data);
|
||||
}
|
||||
|
||||
updateHistory(input: string): void {
|
||||
if ((this.user.history.length = 255)) {
|
||||
this.user.history.unshift(...this.user.history.splice(-1));
|
||||
this.user.history[0] = input;
|
||||
} else {
|
||||
this.user.history.push(input);
|
||||
this.user.history.unshift(...this.user.history.splice(-1));
|
||||
}
|
||||
}
|
||||
|
||||
getCwd(): number {
|
||||
return this.vfs.cwd;
|
||||
}
|
||||
|
||||
getPwd(): number {
|
||||
return this.vfs.pwd;
|
||||
}
|
||||
|
||||
getUser(): User {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
getFs(): VirtualFS {
|
||||
return this.vfs;
|
||||
}
|
||||
|
||||
getTerminalWidth(): number {
|
||||
return this._terminal.getTerminalWidth();
|
||||
}
|
||||
|
||||
getTerminalFontSize(): number {
|
||||
return this._terminal.getFontSize();
|
||||
}
|
||||
|
||||
hasSudoPerms(uid: number): boolean {
|
||||
return this._group[1].members.includes(uid);
|
||||
}
|
||||
|
||||
executeCommand(commandName: string, args: CommandArgs): void {
|
||||
let result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd() };
|
||||
const command = this._commands[commandName];
|
||||
if (!command) this.throwError(result);
|
||||
|
||||
if (command.root) {
|
||||
if (this.hasSudoPerms(this.user.uid)) {
|
||||
let out: Result = command.method.call(this, args);
|
||||
this._appendNewResult(this.getCwd(), out, this.user.history[0]);
|
||||
}
|
||||
this.throwError(result);
|
||||
}
|
||||
|
||||
let out: Result = command.method.call(this, args);
|
||||
console.log(out);
|
||||
this._appendNewResult(out.path, out.data?.data, this.user.history[0]);
|
||||
}
|
||||
|
||||
throwError(result: Result): void {
|
||||
switch (result.exitCode) {
|
||||
default: {
|
||||
throw new Error(`Error, dont know where, just look for it;`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userLogout() {
|
||||
this._instances.pop();
|
||||
if (this._instances.size() === 0) {
|
||||
//TODO: Implement system logout
|
||||
} else {
|
||||
//this.changeUser(this._instances.peek()!);
|
||||
}
|
||||
}
|
||||
|
||||
formatBytes(bytes: number, dPoint?: number, pow: 1024 | 1000 = 1024): string {
|
||||
if (!+bytes) return '0';
|
||||
|
||||
const k: number = pow;
|
||||
const dp: number = dPoint ? (dPoint < 0 ? 0 : dPoint) : 1;
|
||||
const units: string[] = ['', 'K', 'M', 'G', 'T', 'P'];
|
||||
|
||||
const i: number = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${(bytes / Math.pow(k, i)).toFixed(dp)}${units[i]}`;
|
||||
}
|
||||
|
||||
getGroupByName(name: string): Group {
|
||||
const out: Group | undefined = this._group.find((group) => group.groupname === name);
|
||||
|
||||
if (out) return out;
|
||||
else throw new Error(`Cannot find a user group named ${name}`);
|
||||
}
|
||||
|
||||
getUserByName(name: string): User {
|
||||
const out: User | undefined = this._passwd.find((user) => user.username === name);
|
||||
|
||||
if (out) return out;
|
||||
else throw new Error(`Cannot find a user named ${name}`);
|
||||
}
|
||||
|
||||
getGroupByGid(gid: number): Group {
|
||||
const out: Group | undefined = this._group.find((group) => group.gid === gid);
|
||||
|
||||
if (out) return out;
|
||||
else throw new Error(`Cannot find a user group with id of ${gid}`);
|
||||
}
|
||||
|
||||
getUserByUid(uid: number): User {
|
||||
const out: User | undefined = this._passwd.find((user) => user.uid === uid);
|
||||
|
||||
if (out) return out;
|
||||
else throw new Error(`Cannot find a user with id of ${uid}`);
|
||||
}
|
||||
}
|
||||
46
src/lib/stores/bash/commands/cd.ts
Executable file
46
src/lib/stores/bash/commands/cd.ts
Executable file
@@ -0,0 +1,46 @@
|
||||
import { ExitCode, type Bash } from '../bash';
|
||||
import { Type, type TreeNode } from '../fs';
|
||||
import type { CommandArgs, ICommand, Result } from '../static';
|
||||
|
||||
export const cmd_cd = function (this: Bash, args: CommandArgs): Result {
|
||||
let result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd() };
|
||||
const path = args.args[0];
|
||||
let targetNode: TreeNode | null;
|
||||
|
||||
if (args.args.length > 1) return result; // Too many args
|
||||
|
||||
// if no args cd into home dir
|
||||
|
||||
if (args.args.length === 0) {
|
||||
this.getFs().cwd = this.getFs().home;
|
||||
result.exitCode = ExitCode.SUCCESS;
|
||||
return result;
|
||||
}
|
||||
|
||||
// if the arg is - cd make your current dir the prev dir and vice versa
|
||||
|
||||
if (args.args[0] === '-') {
|
||||
[this.getFs().cwd, this.getFs().pwd] = [this.getFs().pwd, this.getFs().cwd];
|
||||
result.exitCode = ExitCode.SUCCESS;
|
||||
return result;
|
||||
}
|
||||
|
||||
this.getFs().pwd = this.getFs().cwd;
|
||||
targetNode = this.getFs().resolvePath(path); // Conversion from STRING path to TREENODE
|
||||
console.log(targetNode, path, 'CD OUTPUT');
|
||||
|
||||
if (targetNode === null) return result;
|
||||
if (targetNode.type !== Type.Directory) return result;
|
||||
//if () return ExitCode.ERROR; // Check for read permissions on node and user
|
||||
|
||||
this.getFs().cwd = targetNode.inode; // CD was successfull, change current dir to the verified target dir
|
||||
result.exitCode = ExitCode.SUCCESS;
|
||||
return result;
|
||||
};
|
||||
|
||||
export const cd: ICommand = {
|
||||
method: cmd_cd,
|
||||
flags: [] as string[],
|
||||
help: 'PATH TO HELP.md',
|
||||
root: false
|
||||
};
|
||||
15
src/lib/stores/bash/commands/clear.ts
Normal file
15
src/lib/stores/bash/commands/clear.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ExitCode, type Bash } from "../bash";
|
||||
import type { CommandArgs, ICommand, Result } from "../static";
|
||||
|
||||
export const cmd_clear = function(this: Bash, args: CommandArgs): Result {
|
||||
let result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd() };
|
||||
result.exitCode = ExitCode.SUCCESS;
|
||||
return result;
|
||||
}
|
||||
|
||||
export const clear: ICommand = {
|
||||
method: cmd_clear,
|
||||
flags: [] as string[],
|
||||
help: 'PATH TO HELP.md',
|
||||
root: false
|
||||
};
|
||||
439
src/lib/stores/bash/commands/ls.ts
Executable file
439
src/lib/stores/bash/commands/ls.ts
Executable file
@@ -0,0 +1,439 @@
|
||||
import { Bash, ExitCode, type Permission, type TimeStamps } from '../bash';
|
||||
import { Type, VirtualFS, type NodePerms, type TreeNode } from '../fs';
|
||||
import { Sort, SortNodeBy } from '../sort';
|
||||
import type { CommandArgs, ICommand, Result, resultData } from '../static';
|
||||
|
||||
type LsEntry = {
|
||||
inode: number | null;
|
||||
perms: string;
|
||||
children: string;
|
||||
owners: string;
|
||||
size: string;
|
||||
modt: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const LsEntryUtils = {
|
||||
toString(entry: LsEntry): string {
|
||||
return Object.entries(entry)
|
||||
.filter(([_, value]) => value !== '')
|
||||
.map(([_, value]) => value)
|
||||
.join(' ').trimStart();
|
||||
}
|
||||
};
|
||||
|
||||
const months: readonly string[] = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec'
|
||||
];
|
||||
|
||||
export const cmd_ls = function (this: Bash, args: CommandArgs): Result {
|
||||
const Fs = this.getFs();
|
||||
const resultData: resultData = { cmd: 'ls', data: null, args: args };
|
||||
const result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd(), data: resultData };
|
||||
const nodes: TreeNode[] = [];
|
||||
|
||||
//Check if any args contain the long flags with value and are valid flags inside the ls const
|
||||
const valuedArgs = args.flags.filter((flag: string) =>
|
||||
flag.includes('=') && ls.flags.includes(flag.split('=')[0]));
|
||||
console.log(valuedArgs);
|
||||
|
||||
//Check if args contain any nonexistent flags, if so add it to an array and check its length. if 0 no bad flags
|
||||
const invalidArgs = args.flags.filter((flag) => !ls.flags.includes(flag) && !valuedArgs.includes(flag));
|
||||
console.log(invalidArgs);
|
||||
|
||||
if (invalidArgs.length > 0) {
|
||||
this.throwError(result); //No such flag/s
|
||||
}
|
||||
|
||||
if (args.args.length === 0) nodes.push(Fs.getNodeByINode(Fs.cwd));
|
||||
|
||||
for (let i = 0; i < args.args.length; i++) {
|
||||
const node = Fs.resolvePath(args.args[i]);
|
||||
if (node === null) this.throwError(result); //no such path (i think this will never occur as backed methods have error cases implemented - which is wrong)
|
||||
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
result.exitCode = ExitCode.SUCCESS;
|
||||
resultData.data = result_ls.call(this, nodes, args);
|
||||
return result;
|
||||
};
|
||||
|
||||
function result_ls(this: Bash, data: any, args: CommandArgs): HTMLElement {
|
||||
const dummysonoerror: HTMLElement = document.createElement('div');
|
||||
const Fs: VirtualFS = this.getFs();
|
||||
|
||||
|
||||
const flagInfo = checkFlags(args.flags);
|
||||
const nodes: TreeNode[] = data;
|
||||
let nodeIndex: number = 0;
|
||||
|
||||
const f_a: boolean = flagInfo.has('a') || flagInfo.has('all');
|
||||
const f_A: boolean = flagInfo.has('A') || flagInfo.has('almost-all');
|
||||
const f_G: boolean = flagInfo.has('G') || flagInfo.has('no-group');
|
||||
const f_h: boolean = flagInfo.has('h') || flagInfo.has('human-readable');
|
||||
const f_r: boolean = flagInfo.has('r') || flagInfo.has('reverse');
|
||||
const f_R: boolean = flagInfo.has('R') || flagInfo.has('recursive');
|
||||
const f_Q: boolean = flagInfo.has('Q') || flagInfo.has('quote-name');
|
||||
const f_n: boolean = flagInfo.has('n') || flagInfo.has('numeric-uid-gid');
|
||||
const f_N: boolean = flagInfo.has('N') || flagInfo.has('literal');
|
||||
const f_L: boolean = flagInfo.has('L') || flagInfo.has('dereference');
|
||||
const f_i: boolean = flagInfo.has('i') || flagInfo.has('inode');
|
||||
const f_help: boolean = flagInfo.has('help');
|
||||
const f_si: boolean = flagInfo.has('si');
|
||||
const f_X: boolean = flagInfo.has('X');
|
||||
const f_S: boolean = flagInfo.has('S');
|
||||
const f_t: boolean = flagInfo.has('t');
|
||||
const f_l: boolean = flagInfo.has('l');
|
||||
const f_U: boolean = flagInfo.has('U');
|
||||
const f_f: boolean = flagInfo.has('f');
|
||||
const f_g: boolean = flagInfo.has('g');
|
||||
const f_o: boolean = flagInfo.has('o');
|
||||
|
||||
const valuedArgs = args.flags.filter((flag: string) =>
|
||||
flag.includes('=') && ls.flags.includes(flag.split('=')[0]));
|
||||
|
||||
const w: HTMLElement = document.createElement('div');
|
||||
|
||||
if(f_R) {
|
||||
const treeWalkResult: TreeNode[] = [];
|
||||
const treeWalkCallback = (node: TreeNode) => { treeWalkResult.push(node); }
|
||||
|
||||
for(const node of nodes) {
|
||||
treeWalkResult.push(node);
|
||||
Fs.recursiveTraversalPre(node, treeWalkCallback);
|
||||
}
|
||||
nodes.length = 0;
|
||||
nodes.push(...treeWalkResult.filter((node) => node.type != Type.File));
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
const elem: HTMLElement = document.createElement('div');
|
||||
const childrenMap: TreeNode[] = node.children.map((child) => Fs.getNodeByINode(child));
|
||||
|
||||
const children: TreeNode[] = (f_a || f_A)
|
||||
? childrenMap
|
||||
: childrenMap.filter((child) => !child.name.startsWith('.'));
|
||||
|
||||
const timeArg = valuedArgs.find((flag) => flag.startsWith('time'));
|
||||
const shouldNamesShift: boolean = children.some((child) => child.name.match(/\s/) !== null);
|
||||
let timestamp: SortNodeBy.ATIME | SortNodeBy.CTIME | SortNodeBy.MTIME = SortNodeBy.MTIME;
|
||||
|
||||
if(f_a && !f_A) {
|
||||
const current: TreeNode = node;
|
||||
current.name = '.';
|
||||
|
||||
const parent: TreeNode = Fs.getNodeByINode(node.parent);
|
||||
parent.name = '..';
|
||||
|
||||
children.unshift(current, parent);
|
||||
}
|
||||
|
||||
if(timeArg) {
|
||||
let value: string = timeArg.split('=')[1];
|
||||
if (value && isValidNodeTimestamp(value)) timestamp = value;
|
||||
}
|
||||
|
||||
if (!f_U && !f_f) {
|
||||
const sortArg = valuedArgs.find((flag) => flag.startsWith('sort'));
|
||||
let sortBy: SortNodeBy = SortNodeBy.NAME;
|
||||
|
||||
if(f_t) sortBy = timestamp;
|
||||
if(f_S) sortBy = SortNodeBy.SIZE;
|
||||
if(f_X) sortBy = SortNodeBy.EXTENSION;
|
||||
|
||||
if(sortArg) {
|
||||
let value = sortArg.split('=')[1];
|
||||
if(value && isValidNodeSortMethod(value)) sortBy = value;
|
||||
}
|
||||
|
||||
Sort.nodeArraySort.call(this, children, f_r, sortBy);
|
||||
}
|
||||
|
||||
if (f_l || f_g || f_o) {
|
||||
const rows: string[] = [];
|
||||
const maxSizeWidth = Math.max(
|
||||
...children.map((child) => child.size.toString().length));
|
||||
|
||||
for (const child of children) {
|
||||
const entry: LsEntry = {
|
||||
inode: null,
|
||||
perms: formatPermission(child),
|
||||
children: formatChildren(child),
|
||||
owners: formatOwners.call(this, child, flagInfo),
|
||||
size: formatSize.call(this, f_h, child, maxSizeWidth, f_si),
|
||||
modt: formatModtime(child, timestamp),
|
||||
name: formatName(child, flagInfo, shouldNamesShift)
|
||||
};
|
||||
|
||||
if (f_i) entry.inode = child.inode;
|
||||
|
||||
rows.push(LsEntryUtils.toString(entry));
|
||||
}
|
||||
|
||||
//TODO: Calculate the total size of contents in the node
|
||||
rows.unshift('total ' + node.children.length.toString());
|
||||
|
||||
if (nodes.length > 1) {
|
||||
const nodePath: HTMLElement = document.createElement('p');
|
||||
nodePath.innerText = `${Fs.getPathByInode(node.inode)}:`;
|
||||
w.appendChild(nodePath);
|
||||
if(nodeIndex +1 < nodes.length) elem.style.marginBottom = `${this.getTerminalFontSize() * 2}px`;
|
||||
}
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const p: HTMLElement = document.createElement('p');
|
||||
p.innerText = rows[i];
|
||||
elem.appendChild(p);
|
||||
}
|
||||
w.appendChild(elem);
|
||||
}
|
||||
else {
|
||||
const maxWidth: number = Math.ceil((this.getTerminalWidth() / this.getTerminalFontSize()) * 0.8);
|
||||
|
||||
let columns: string[][] = [];
|
||||
let colWidths: number[];
|
||||
let lowBound: number = 1;
|
||||
let highBound: number = children.length;
|
||||
|
||||
while(lowBound + 1 < highBound) {
|
||||
const c = lowBound + (highBound - lowBound) / 2;
|
||||
|
||||
columns = [];
|
||||
colWidths = [];
|
||||
let fileIndex = 0;
|
||||
|
||||
for(let i = 0; i < c; i++) {
|
||||
if(fileIndex >= children.length) break;
|
||||
columns[i] = [];
|
||||
for(let j = 0; j < Math.ceil(children.length / c); j++) {
|
||||
if(fileIndex >= children.length) break;
|
||||
columns[i].push(children[fileIndex].name);
|
||||
fileIndex++;
|
||||
}
|
||||
colWidths.push(Math.max(...columns[i].map((name) => name.length)));
|
||||
}
|
||||
|
||||
const calcWidth: number = colWidths.reduce((prev, curr) => prev + curr) + ((c-1) * 3);
|
||||
|
||||
if(calcWidth < maxWidth) lowBound = c + 1;
|
||||
else if(calcWidth > maxWidth) highBound = c -1
|
||||
}
|
||||
|
||||
const wrapper: HTMLElement = document.createElement('div');
|
||||
|
||||
wrapper.style.display = 'flex';
|
||||
wrapper.style.marginBottom = `${this.getTerminalFontSize() * 2}px`;
|
||||
|
||||
let fileIndex = 0;
|
||||
|
||||
for(let i = 0; i < lowBound; i++) {
|
||||
if(fileIndex >= children.length) break;
|
||||
const col: HTMLElement = document.createElement('div');
|
||||
|
||||
for(let j = 0; j < Math.ceil(children.length / lowBound); j++) {
|
||||
if(fileIndex >= children.length) break;
|
||||
|
||||
const entry: HTMLElement = document.createElement('p');
|
||||
|
||||
entry.innerText = formatName(children[fileIndex], flagInfo, shouldNamesShift);
|
||||
col.appendChild(entry);
|
||||
fileIndex++;
|
||||
}
|
||||
wrapper.appendChild(col);
|
||||
}
|
||||
|
||||
if (nodes.length > 1) {
|
||||
const nodePath: HTMLElement = document.createElement('p');
|
||||
nodePath.innerText = `${Fs.getPathByInode(node.inode)}`;
|
||||
w.appendChild(nodePath);
|
||||
}
|
||||
|
||||
w.appendChild(wrapper);
|
||||
}
|
||||
nodeIndex++;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
function isValidNodeSortMethod(value: string): value is SortNodeBy {
|
||||
return Object.values(SortNodeBy).includes(value as SortNodeBy);
|
||||
}
|
||||
|
||||
function isValidNodeTimestamp(value: string): value is SortNodeBy.ATIME | SortNodeBy.CTIME | SortNodeBy.MTIME {
|
||||
return Object.values(SortNodeBy).includes(value as SortNodeBy.ATIME | SortNodeBy.CTIME | SortNodeBy.MTIME);
|
||||
}
|
||||
|
||||
function parsePerms(perms: NodePerms): string {
|
||||
const parts: string[] = [];
|
||||
//for each key (key representing key name and p representing the key contents) of entries in perms as types keyof NodePerms and Permission
|
||||
for (const [key, p] of Object.entries(perms) as [keyof NodePerms, Permission][]) {
|
||||
const perms: string = `${p.r ? 'r' : '-'}${p.w ? 'w' : '-'}${p.x ? 'x' : '-'}`;
|
||||
parts.push(perms);
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function formatOwners(this: Bash, node: TreeNode, flag: any): string {
|
||||
const owner: string = this.getUserByUid(node.owner).username;
|
||||
const group: string = this.getGroupByGid(node.group).groupname;
|
||||
|
||||
if (flag.has('G') || flag.has('o')) {
|
||||
if (flag.has('n')) return `${node.owner}`;
|
||||
return `${owner}`;
|
||||
}
|
||||
|
||||
if (flag.has('g')) {
|
||||
if (flag.has('n')) return `${node.group}`;
|
||||
return `${group}`;
|
||||
}
|
||||
|
||||
if (flag.has('n')) return `${node.owner} ${node.group}`;
|
||||
|
||||
return `${owner} ${group}`;
|
||||
}
|
||||
|
||||
function formatPermission(node: TreeNode): string {
|
||||
switch(node.type) {
|
||||
case Type.Directory:
|
||||
return `d${parsePerms(node.permission)}`;
|
||||
case Type.File:
|
||||
return `-${parsePerms(node.permission)}`;
|
||||
case Type.SymbolicLink:
|
||||
return `l${parsePerms(node.permission)}`;
|
||||
default:
|
||||
throw new Error(`Node of unexpected type - ${node.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function formatChildren(node: TreeNode): string {
|
||||
if (node.type !== Type.Directory) return ' 0';
|
||||
if (!node.children) throw new Error('children array is null on this node');
|
||||
|
||||
const c = node.children.length.toString();
|
||||
console.log(c, "TEST TEST");
|
||||
return c.length > 1 ? c : ` ${c}`;
|
||||
}
|
||||
|
||||
function formatSize(
|
||||
this: Bash,
|
||||
humanReadable: boolean,
|
||||
node: TreeNode,
|
||||
max: number,
|
||||
f_si: boolean
|
||||
): string {
|
||||
let size: string;
|
||||
if (humanReadable) {
|
||||
size = this.formatBytes(node.size, 1, f_si ? 1000 : 1024);
|
||||
} else size = node.size.toString();
|
||||
|
||||
return size.padStart(max, ' ');
|
||||
}
|
||||
|
||||
function formatModtime(node: TreeNode, sortBy: SortNodeBy.ATIME | SortNodeBy.CTIME | SortNodeBy.MTIME): string {
|
||||
const now = new Date();
|
||||
//TODO: Change this to be dynamic based on the --time value passed
|
||||
const hours: string = node.timestamps[sortBy].getHours().toString().padStart(2, '0');
|
||||
const minutes: string = node.timestamps[sortBy].getMinutes().toString().padStart(2, '0');
|
||||
const time: string =
|
||||
now.getFullYear() === node.timestamps[sortBy].getFullYear()
|
||||
? `${hours}:${minutes}`
|
||||
: node.timestamps[sortBy].getFullYear().toString();
|
||||
|
||||
return [
|
||||
months[node.timestamps[sortBy].getMonth()],
|
||||
node.timestamps[sortBy].getDate().toString().padStart(2, ' '),
|
||||
`${time}`
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
function formatName(node: TreeNode, flag: any, shouldShift: boolean) {
|
||||
let name: string = node.name;
|
||||
const char: string = flag.has('Q') ? '"' : "'";
|
||||
|
||||
if (/\s/.test(node.name)) name = `${char}${name}${char}`;
|
||||
|
||||
if(flag.has('p') && node.type === Type.Directory) name = `${name}/`
|
||||
|
||||
if((flag.has('l') || flag.has('g') || flag.has('o'))) {
|
||||
// Is the ls in long format
|
||||
if (!(/\s/.test(node.name))) {
|
||||
//Shift non quoted names 1 char right to align if any names in group have a quote
|
||||
name = `${shouldShift ? ' ' : ''}${name}`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!(/\s/.test(node.name))) {
|
||||
name = ` ${name}`;
|
||||
}
|
||||
|
||||
name = `${name} `;
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
const checkFlags = (pFlags: string[]) => {
|
||||
const flagSet = new Set(pFlags);
|
||||
|
||||
return { has: (flag: string) => flagSet.has(flag) };
|
||||
};
|
||||
|
||||
export const ls: ICommand = {
|
||||
method: cmd_ls,
|
||||
flags: [
|
||||
'l',
|
||||
'a',
|
||||
'A',
|
||||
'c',
|
||||
'U',
|
||||
'g',
|
||||
'G',
|
||||
'h',
|
||||
'f',
|
||||
'x',
|
||||
'X',
|
||||
'u',
|
||||
't',
|
||||
'S',
|
||||
'r',
|
||||
'R',
|
||||
'Q',
|
||||
'p',
|
||||
'o',
|
||||
'n',
|
||||
'N',
|
||||
'L',
|
||||
'm',
|
||||
'i',
|
||||
'sort',
|
||||
'time',
|
||||
'help',
|
||||
'all',
|
||||
'almost-all',
|
||||
'no-group',
|
||||
'human-readable',
|
||||
'reverse',
|
||||
'recursive',
|
||||
'quote-name',
|
||||
'indicator-style',
|
||||
'literal',
|
||||
'numeric-uid-gid',
|
||||
'inode',
|
||||
'si',
|
||||
'dereference'
|
||||
] as string[],
|
||||
help: 'PATH TO HELP.MD',
|
||||
root: false
|
||||
};
|
||||
13
src/lib/stores/bash/commands/ls2.ts
Normal file
13
src/lib/stores/bash/commands/ls2.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { ICommand } from "../static"
|
||||
import { cmd_ls } from "./ls"
|
||||
|
||||
type LsEntry
|
||||
|
||||
|
||||
|
||||
|
||||
export class ls {
|
||||
public const ls: ICommand = {
|
||||
method: ls.cmd_ls
|
||||
}
|
||||
}
|
||||
175
src/lib/stores/bash/fs.ts
Executable file
175
src/lib/stores/bash/fs.ts
Executable file
@@ -0,0 +1,175 @@
|
||||
import type { Permission, TimeStamps, User } from './bash';
|
||||
|
||||
export enum Type {
|
||||
Directory = 16384,
|
||||
File = 32768,
|
||||
SymbolicLink = 40960
|
||||
}
|
||||
|
||||
export type NodePerms = {
|
||||
user: Permission;
|
||||
group: Permission;
|
||||
other: Permission;
|
||||
};
|
||||
|
||||
export type FsInitArgs = {
|
||||
fs: any;
|
||||
user: User;
|
||||
};
|
||||
|
||||
export type TreeNode = {
|
||||
inode: number;
|
||||
parent: number;
|
||||
name: string;
|
||||
type: Type;
|
||||
size: number; //Size in Bytes
|
||||
children: number[];
|
||||
content: string; // GUID of the cache file that contains the file contents.
|
||||
link: number; // Links
|
||||
permission: NodePerms;
|
||||
owner: number;
|
||||
group: number;
|
||||
timestamps: TimeStamps;
|
||||
interactible: boolean;
|
||||
func: any;
|
||||
};
|
||||
|
||||
export class VirtualFS {
|
||||
private FsTable: Map<number, TreeNode>;
|
||||
private rootINode: number;
|
||||
|
||||
home: number;
|
||||
cwd: number;
|
||||
pwd: number;
|
||||
|
||||
constructor(args: FsInitArgs) {
|
||||
this.FsTable = args.fs;
|
||||
this.rootINode = 1;
|
||||
this.home = this._pathStringToINode(args.user.home);
|
||||
this.cwd = args.user.cwd ? args.user.cwd : this.home;
|
||||
this.pwd = args.user.pwd ? args.user.pwd : this.cwd;
|
||||
|
||||
console.log(this.home);
|
||||
console.log(this.cwd);
|
||||
console.log(this.pwd);
|
||||
}
|
||||
|
||||
private _iNodeToPathString(inode: number): string {
|
||||
const currentNode = this.FsTable.get(inode);
|
||||
if (!currentNode)
|
||||
throw new Error('could not find the node in the fs table - inodetopathstring');
|
||||
|
||||
if (!currentNode.parent) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
const parentPath: string = this._iNodeToPathString(currentNode.parent);
|
||||
return parentPath === '/' ? `/${currentNode.name}` : `${parentPath}/${currentNode.name}`;
|
||||
}
|
||||
|
||||
//TODO: Make all backend methods NOT throw errors. Just return null, and let more closely connected with bash functions call throwError() so user can see the error.
|
||||
// this will save a lot of ass pain later.
|
||||
|
||||
private _pathStringToINode(path: string): number {
|
||||
const normalizedPath = path.replace(/^\/+|\/+$/g, '');
|
||||
const pathComponents = normalizedPath.split('/').filter((component) => component.length > 0);
|
||||
|
||||
console.log(path, 'pathstringtoinode');
|
||||
|
||||
if (pathComponents.length === 0) return this.rootINode;
|
||||
|
||||
let currentNode = this.FsTable.get(this.rootINode);
|
||||
if (!currentNode) throw new Error('iNode does not exist,');
|
||||
|
||||
for (const component of pathComponents) {
|
||||
const childINode = this._findChildNodeByName(currentNode, component);
|
||||
if (childINode === null) throw new Error('this child iNode does not exist,');
|
||||
|
||||
const nextNode = this.FsTable.get(childINode);
|
||||
if (!nextNode) throw new Error('iNode child does not exist,');
|
||||
|
||||
currentNode = nextNode;
|
||||
}
|
||||
|
||||
console.log(path, currentNode.inode);
|
||||
return currentNode.inode;
|
||||
}
|
||||
|
||||
private _findChildNodeByName(node: TreeNode, name: string): number {
|
||||
console.log(name, node);
|
||||
for (const childINode of node.children) {
|
||||
const child = this.FsTable.get(childINode);
|
||||
if (child && child.name === name) {
|
||||
return childINode;
|
||||
}
|
||||
}
|
||||
throw new Error('could not find the specified child node');
|
||||
}
|
||||
|
||||
private _isAbsolutePath = (path: string): boolean => {
|
||||
return typeof path === 'string' && path.startsWith('/');
|
||||
};
|
||||
|
||||
public getPathByInode(inode: number): string {
|
||||
return this._iNodeToPathString(inode);
|
||||
}
|
||||
|
||||
public formatPath(path: string): string {
|
||||
console.log(path, 'formatPath');
|
||||
const prefix = this._iNodeToPathString(this.home);
|
||||
|
||||
if (path.startsWith(prefix)) {
|
||||
return path.replace(prefix, '~');
|
||||
} else return path;
|
||||
}
|
||||
|
||||
public resolvePath(path: string): TreeNode {
|
||||
if (path === '/') return this.getNodeByINode(this.rootINode);
|
||||
let parsedPath: string = path;
|
||||
|
||||
if (!this._isAbsolutePath(path)) {
|
||||
const trail: string = this._iNodeToPathString(this.cwd);
|
||||
parsedPath = `${trail}/${path}`;
|
||||
console.log(parsedPath);
|
||||
}
|
||||
if (path.startsWith('~')) {
|
||||
const trail: string = this._iNodeToPathString(this.home);
|
||||
parsedPath = `${trail}/${path.replace('~', '')}`;
|
||||
console.log(parsedPath);
|
||||
}
|
||||
|
||||
const INode: number = this._pathStringToINode(parsedPath);
|
||||
const Node: TreeNode = this.getNodeByINode(INode);
|
||||
|
||||
return Node;
|
||||
}
|
||||
|
||||
public getNodeByINode(inode: number): TreeNode {
|
||||
const node: TreeNode | undefined = this.FsTable.get(inode);
|
||||
if (!node) throw new Error(`Could not get the node, no such inode exists - ${inode}`);
|
||||
return node;
|
||||
}
|
||||
|
||||
public recursiveTraversalPre(node: TreeNode, callback: (param: TreeNode) => void, childIndex?: number[], depthIndex?: number): void {
|
||||
if(!depthIndex) depthIndex = 0;
|
||||
if(!childIndex) childIndex = [0];
|
||||
|
||||
if(node.type != Type.File && node.children[childIndex[depthIndex]]) {
|
||||
node = this.getNodeByINode(node.children[childIndex[depthIndex]]);
|
||||
depthIndex++;
|
||||
if(!childIndex[depthIndex]) childIndex[depthIndex] = 0;
|
||||
|
||||
callback(node);
|
||||
}
|
||||
else {
|
||||
node = this.getNodeByINode(node.parent);
|
||||
childIndex[depthIndex] = 0;
|
||||
depthIndex--;
|
||||
childIndex[depthIndex]++;
|
||||
}
|
||||
|
||||
if(depthIndex < 0) return;
|
||||
|
||||
this.recursiveTraversalPre(node, callback, childIndex, depthIndex);
|
||||
}
|
||||
}
|
||||
78
src/lib/stores/bash/posix.ts
Normal file
78
src/lib/stores/bash/posix.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
export class Posix {
|
||||
private static readonly CharTable = {
|
||||
// Space and punctuation (32-47)
|
||||
' ': 32,
|
||||
'!': 33,
|
||||
'"': 34,
|
||||
'#': 35,
|
||||
'$': 36,
|
||||
'%': 37,
|
||||
'&': 38,
|
||||
"'": 39,
|
||||
'(': 40,
|
||||
')': 41,
|
||||
'*': 42,
|
||||
'+': 43,
|
||||
',': 44,
|
||||
'-': 45,
|
||||
'.': 46,
|
||||
'/': 47,
|
||||
|
||||
// Numbers 0-9 (48-57)
|
||||
'0': 48, '1': 49, '2': 50, '3': 51, '4': 52,
|
||||
'5': 53, '6': 54, '7': 55, '8': 56, '9': 57,
|
||||
|
||||
// More punctuation (58-64)
|
||||
':': 58,
|
||||
';': 59,
|
||||
'<': 60,
|
||||
'=': 61,
|
||||
'>': 62,
|
||||
'?': 63,
|
||||
'@': 64,
|
||||
|
||||
// LETTERS - INTERLEAVED AaBbCc... (65-116)
|
||||
'A': 65, 'a': 66,
|
||||
'B': 67, 'b': 68,
|
||||
'C': 69, 'c': 70,
|
||||
'D': 71, 'd': 72,
|
||||
'E': 73, 'e': 74,
|
||||
'F': 75, 'f': 76,
|
||||
'G': 77, 'g': 78,
|
||||
'H': 79, 'h': 80,
|
||||
'I': 81, 'i': 82,
|
||||
'J': 83, 'j': 84,
|
||||
'K': 85, 'k': 86,
|
||||
'L': 87, 'l': 88,
|
||||
'M': 89, 'm': 90,
|
||||
'N': 91, 'n': 92,
|
||||
'O': 93, 'o': 94,
|
||||
'P': 95, 'p': 96,
|
||||
'Q': 97, 'q': 98,
|
||||
'R': 99, 'r': 100,
|
||||
'S': 101, 's': 102,
|
||||
'T': 103, 't': 104,
|
||||
'U': 105, 'u': 106,
|
||||
'V': 107, 'v': 108,
|
||||
'W': 109, 'w': 110,
|
||||
'X': 111, 'x': 112,
|
||||
'Y': 113, 'y': 114,
|
||||
'Z': 115, 'z': 116,
|
||||
|
||||
// Specials after letters (now 117-126)
|
||||
'[': 117,
|
||||
'\\': 118,
|
||||
']': 119,
|
||||
'^': 120,
|
||||
'_': 121,
|
||||
'`': 122,
|
||||
'{': 123,
|
||||
'|': 124,
|
||||
'}': 125,
|
||||
'~': 126
|
||||
} as const;
|
||||
|
||||
public static GetCharWeight(char: string): number {
|
||||
return this.CharTable[char as keyof typeof this.CharTable];
|
||||
}
|
||||
}
|
||||
7
src/lib/stores/bash/search.ts
Normal file
7
src/lib/stores/bash/search.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Bash } from "./bash";
|
||||
import type { TreeNode } from "./fs";
|
||||
|
||||
export class Search {
|
||||
|
||||
}
|
||||
|
||||
142
src/lib/stores/bash/sort.ts
Executable file
142
src/lib/stores/bash/sort.ts
Executable file
@@ -0,0 +1,142 @@
|
||||
import type { TreeNode } from './fs';
|
||||
import type { Bash } from './bash';
|
||||
import { Posix } from './posix';
|
||||
|
||||
export enum SortNodeBy {
|
||||
NAME = 'name',
|
||||
INODE = 'inode',
|
||||
SIZE = 'size',
|
||||
EXTENSION = 'extension',
|
||||
TYPE = 'type',
|
||||
MTIME = 'modified',
|
||||
ATIME = 'accessed',
|
||||
CTIME = 'changed'
|
||||
}
|
||||
|
||||
export class Sort {
|
||||
public static nodeArraySort(
|
||||
this: Bash,
|
||||
nodes: TreeNode[] | number[],
|
||||
reverse: boolean = false,
|
||||
sortBy: SortNodeBy = SortNodeBy.NAME
|
||||
): TreeNode[] {
|
||||
if (nodes.length === 0) {console.warn('Tried to sort an empty node array!'); return [];}
|
||||
const parsedNodes: TreeNode[] = [];
|
||||
|
||||
if (typeof nodes[0] === 'number') {
|
||||
for (const inode of nodes as number[]) {
|
||||
const node = this.getFs().getNodeByINode(inode);
|
||||
parsedNodes.push(node);
|
||||
}
|
||||
Sort.nodeQSort(parsedNodes, reverse, sortBy, 0, parsedNodes.length - 1);
|
||||
console.log(parsedNodes);
|
||||
return parsedNodes;
|
||||
} else {
|
||||
Sort.nodeQSort(nodes as TreeNode[], reverse, sortBy, 0, nodes.length - 1);
|
||||
console.log(nodes);
|
||||
return nodes as TreeNode[];
|
||||
}
|
||||
}
|
||||
|
||||
private static nodeQSort(
|
||||
array: TreeNode[],
|
||||
reverse: boolean,
|
||||
sortBy: SortNodeBy,
|
||||
start: number,
|
||||
end: number
|
||||
) {
|
||||
if (end <= start) return;
|
||||
|
||||
let pivot: number = this.nodePartition(array, reverse, sortBy, start, end);
|
||||
this.nodeQSort(array, reverse, sortBy, start, pivot - 1);
|
||||
this.nodeQSort(array, reverse, sortBy, pivot + 1, end);
|
||||
}
|
||||
|
||||
private static nodePartition(
|
||||
part: TreeNode[],
|
||||
reverse: boolean,
|
||||
sortBy: SortNodeBy,
|
||||
start: number,
|
||||
end: number
|
||||
): number {
|
||||
let pivot: TreeNode = part[end];
|
||||
let i: number = start - 1;
|
||||
|
||||
for (let j = start; j <= end; j++) {
|
||||
if (this.nodeCompareElements(part[j], pivot, sortBy, reverse) < 0) {
|
||||
i++;
|
||||
let temp = part[i];
|
||||
part[i] = part[j];
|
||||
part[j] = temp;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
let temp = part[i];
|
||||
part[i] = part[end];
|
||||
part[end] = temp;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private static nodeCompareElements(
|
||||
a: TreeNode,
|
||||
b: TreeNode,
|
||||
sortBy: SortNodeBy,
|
||||
reverse: boolean
|
||||
): number {
|
||||
switch (sortBy) {
|
||||
case SortNodeBy.NAME: {
|
||||
const nameA: string = a.name.startsWith('.') ? a.name.slice(1) : a.name;
|
||||
const nameB: string = b.name.startsWith('.') ? b.name.slice(1) : b.name;
|
||||
const minLength = Math.min(nameA.length, nameB.length);
|
||||
|
||||
for (let i = 0; i < minLength; i++) {
|
||||
const charCodeA = Posix.GetCharWeight(nameA[i]);
|
||||
const charCodeB = Posix.GetCharWeight(nameB[i]);
|
||||
|
||||
if (charCodeA !== charCodeB) {
|
||||
return reverse ? charCodeB - charCodeA : charCodeA - charCodeB;
|
||||
}
|
||||
}
|
||||
return reverse ? nameB.length - nameA.length : nameA.length - nameB.length;
|
||||
}
|
||||
case SortNodeBy.MTIME:
|
||||
case SortNodeBy.ATIME:
|
||||
case SortNodeBy.CTIME: {
|
||||
console.log(sortBy, 'sortby');
|
||||
// The sortBy serves as the lookup key in the timestamps object.
|
||||
// It works because the times in SortBy enum have assigned values matching the names of the keys in the TreeNode object
|
||||
const timeA: number = a.timestamps[sortBy].getTime();
|
||||
const timeB: number = b.timestamps[sortBy].getTime();
|
||||
|
||||
return reverse ? timeA - timeB : timeB - timeA;
|
||||
}
|
||||
case SortNodeBy.SIZE: {
|
||||
return reverse ? a.size - b.size : b.size - a.size;
|
||||
}
|
||||
case SortNodeBy.EXTENSION: {
|
||||
const extA: string = a.name.split('.').pop() ?? '';
|
||||
const extB: string = b.name.split('.').pop() ?? '';
|
||||
const minLength = Math.min(extA.length, extB.length);
|
||||
|
||||
for (let i = 0; i < minLength; i++) {
|
||||
const charCodeA = extA.charCodeAt(i);
|
||||
const charCodeB = extB.charCodeAt(i);
|
||||
|
||||
if (charCodeA !== charCodeB) {
|
||||
return reverse ? charCodeB - charCodeA : charCodeA - charCodeB;
|
||||
}
|
||||
}
|
||||
return reverse ? extB.length - extA.length : extA.length - extB.length;
|
||||
}
|
||||
case SortNodeBy.INODE: {
|
||||
return reverse ? b.inode - a.inode : a.inode - b.inode;
|
||||
}
|
||||
case SortNodeBy.TYPE: {
|
||||
return reverse ? b.type - a.type : a.type - b.type;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Sorting basis outside of the declared scope. - `);
|
||||
}
|
||||
}
|
||||
}
|
||||
200
src/lib/stores/bash/static.ts
Executable file
200
src/lib/stores/bash/static.ts
Executable file
@@ -0,0 +1,200 @@
|
||||
import { Bash, ExitCode, type Group, type User } from './bash';
|
||||
import { ls } from './commands/ls';
|
||||
import { cd } from './commands/cd';
|
||||
import { clear } from './commands/clear';
|
||||
|
||||
export type ICommand = {
|
||||
method: (this: Bash, args: CommandArgs) => Result;
|
||||
flags: string[];
|
||||
help: string;
|
||||
root: boolean;
|
||||
};
|
||||
|
||||
export type CommandArgs = {
|
||||
flags: string[];
|
||||
args: string[];
|
||||
};
|
||||
|
||||
export type resultData = {
|
||||
cmd: string; //the string that contains the shorthand for the command that was executed - used in a switch statement in parseResult
|
||||
data: any; //the data that the commmand may have returned like TreeNodes[] from ls
|
||||
args?: CommandArgs;
|
||||
};
|
||||
|
||||
export type Result = {
|
||||
exitCode: ExitCode;
|
||||
path: number; //the inode of the place that the command was executed in
|
||||
data?: resultData;
|
||||
};
|
||||
|
||||
export const GROUP: Group[] = [
|
||||
{
|
||||
groupname: 'sudo',
|
||||
gid: 69,
|
||||
members: [0, 1001]
|
||||
},
|
||||
{
|
||||
groupname: 'users',
|
||||
gid: 984,
|
||||
members: [1001, 1002]
|
||||
}
|
||||
] as const;
|
||||
|
||||
export const PASSWD: User[] = [
|
||||
{
|
||||
username: 'root',
|
||||
passwd: '123',
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
home: '/',
|
||||
history: [] //TODO: Delete this and declare a new history array when logging the user in.
|
||||
},
|
||||
{
|
||||
username: 'admin',
|
||||
passwd: '456',
|
||||
uid: 1000,
|
||||
gid: 1000,
|
||||
home: '/home/admin',
|
||||
history: [] //TODO: Delete this and declare a new history array when logging the user in.
|
||||
},
|
||||
{
|
||||
username: 'user',
|
||||
passwd: '789',
|
||||
uid: 1001,
|
||||
gid: 1000,
|
||||
home: '/home/user',
|
||||
history: [] //TODO: Delete this and declare a new history array when logging the user in.
|
||||
},
|
||||
{
|
||||
username: 'kamil',
|
||||
passwd: '000',
|
||||
uid: 1002,
|
||||
gid: 1000,
|
||||
home: '/home/kamil',
|
||||
history: [] //TODO: Delete this and declare a new history array when logging the user in.
|
||||
}
|
||||
];
|
||||
|
||||
export const COMMANDS = {
|
||||
cd,
|
||||
ls,
|
||||
clear
|
||||
} as const satisfies Record<string, ICommand>;
|
||||
|
||||
/* //export const commands {
|
||||
return: {
|
||||
method: this.cmd_return,
|
||||
flags: [],
|
||||
help: "Help about this command",
|
||||
},
|
||||
ls: {
|
||||
method: this.cmd_ls,
|
||||
flags: [],
|
||||
help: "./help/ls.md",
|
||||
},
|
||||
echo: {
|
||||
method: this.cmd_echo,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
touch: {
|
||||
method: this.cmd_touch,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
mkdir: {
|
||||
method: this.cmd_mkdir,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
pwd: {
|
||||
method: this.cmd_pwd,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
cd: {
|
||||
method: this.cmd_cd,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
exit: {
|
||||
method: this.cmd_exit,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
cp: {
|
||||
method: this.cmd_cp,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
mv: {
|
||||
method: this.cmd_mv,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
rmdir: {
|
||||
method: this.cmd_rmdir,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
cat: {
|
||||
method: this.cmd_cat,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
dir: {
|
||||
method: this.cmd_dir,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
less: {
|
||||
method: this.cmd_less,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
chown: {
|
||||
method: this.cmd_chown,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
chmod: {
|
||||
method: this.cmd_chmod,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
reboot: {
|
||||
method: this.cmd_reboot,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
help: {
|
||||
method: this.cmd_help,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
whoami: {
|
||||
method: this.cmd_whoami,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
rm: {
|
||||
method: this.cmd_rm,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
sudo: {
|
||||
method: this.cmd_sudo,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
su: {
|
||||
method: this.cmd_su,
|
||||
flags: [],
|
||||
help: "",
|
||||
},
|
||||
clear: {
|
||||
method: this.cmd_clear,
|
||||
flags: [],
|
||||
help: "",
|
||||
}
|
||||
} */
|
||||
72
src/lib/stores/char.ts
Executable file
72
src/lib/stores/char.ts
Executable file
@@ -0,0 +1,72 @@
|
||||
type BrandedChar = string & { readonly __brand: unique symbol };
|
||||
|
||||
export class Char {
|
||||
private readonly value: BrandedChar;
|
||||
|
||||
private constructor(char: string) {
|
||||
if (char.length !== 1) {
|
||||
throw new Error('Char must be exactly one character');
|
||||
}
|
||||
this.value = char as BrandedChar;
|
||||
}
|
||||
|
||||
static from(char: string): Char {
|
||||
return new Char(char);
|
||||
}
|
||||
|
||||
valueOf(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
charCode(): number {
|
||||
return this.value.charCodeAt(0);
|
||||
}
|
||||
|
||||
isDigit(): boolean {
|
||||
return /^\d$/.test(this.value);
|
||||
}
|
||||
|
||||
isLetter(): boolean {
|
||||
return /^[a-zA-Z]$/.test(this.value);
|
||||
}
|
||||
|
||||
isAlphanumeric(): boolean {
|
||||
return /^[a-zA-Z0-9]$/.test(this.value);
|
||||
}
|
||||
|
||||
isWhitespace(): boolean {
|
||||
return /^\s$/.test(this.value);
|
||||
}
|
||||
|
||||
isUpperCase(): boolean {
|
||||
return this.value === this.value.toUpperCase() && this.value !== this.value.toLowerCase();
|
||||
}
|
||||
|
||||
isLowerCase(): boolean {
|
||||
return this.value === this.value.toLowerCase() && this.value !== this.value.toUpperCase();
|
||||
}
|
||||
|
||||
toUpperCase(): Char {
|
||||
return Char.from(this.value.toUpperCase());
|
||||
}
|
||||
|
||||
toLowerCase(): Char {
|
||||
return Char.from(this.value.toLowerCase());
|
||||
}
|
||||
|
||||
equals(other: Char): boolean {
|
||||
return this.value === other.value;
|
||||
}
|
||||
|
||||
equalsIgnoreCase(other: Char): boolean {
|
||||
return this.value.toLowerCase() === other.value.toLowerCase();
|
||||
}
|
||||
|
||||
static fromCharCode(charCode: number): Char {
|
||||
return Char.from(String.fromCharCode(charCode));
|
||||
}
|
||||
}
|
||||
13
src/lib/stores/lang.ts
Executable file
13
src/lib/stores/lang.ts
Executable file
@@ -0,0 +1,13 @@
|
||||
import plFlag from '$lib/assets/plFlag.svg';
|
||||
import enFlag from '$lib/assets/enFlag.svg';
|
||||
import deFlag from '$lib/assets/deFlag.svg';
|
||||
import frFlag from '$lib/assets/frFlag.svg';
|
||||
import jaFlag from '$lib/assets/jaFlag.svg';
|
||||
|
||||
export const langs = {
|
||||
pl: {},
|
||||
en: {},
|
||||
de: {},
|
||||
ja: {},
|
||||
fr: {}
|
||||
};
|
||||
31
src/lib/stores/stack.ts
Executable file
31
src/lib/stores/stack.ts
Executable file
@@ -0,0 +1,31 @@
|
||||
export class Stack<T> {
|
||||
private items: T[] = [];
|
||||
|
||||
push(element: T): void {
|
||||
this.items.push(element);
|
||||
}
|
||||
|
||||
pop(): T | undefined {
|
||||
return this.items.pop();
|
||||
}
|
||||
|
||||
peek(): T | undefined {
|
||||
return this.items[this.items.length - 1];
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.items.length === 0;
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
toArray(): T[] {
|
||||
return [...this.items];
|
||||
}
|
||||
}
|
||||
125
src/lib/stores/terminal/init.svelte.ts
Executable file
125
src/lib/stores/terminal/init.svelte.ts
Executable file
@@ -0,0 +1,125 @@
|
||||
import type { User } from '../bash/bash';
|
||||
import type { TreeNode } from '../bash/fs';
|
||||
import { Terminal, type TermInitArgs } from './terminal';
|
||||
|
||||
let initializing = $state(true);
|
||||
|
||||
export function isInitializing(): boolean {
|
||||
return initializing;
|
||||
}
|
||||
|
||||
function jsonToNodeTable(data: any): Map<number, TreeNode> {
|
||||
const FsTable: Map<number, TreeNode> = new Map<number, TreeNode>();
|
||||
const entryList = Object.entries(data);
|
||||
|
||||
for (let i = 0; i < entryList.length; i++) {
|
||||
const object: any = entryList[i][1];
|
||||
const node: TreeNode = {
|
||||
inode: object.Inode,
|
||||
name: object.Name,
|
||||
type: object.Type,
|
||||
size: object.Size,
|
||||
interactible: object.Interactible,
|
||||
func: object.Func,
|
||||
children: object.Children,
|
||||
content: object.Content,
|
||||
link: object.Link,
|
||||
permission: {
|
||||
user: {
|
||||
r: object.Permission[0]?.Read,
|
||||
w: object.Permission[0]?.Write,
|
||||
x: object.Permission[0]?.Exec
|
||||
},
|
||||
group: {
|
||||
r: object.Permission[1]?.Read,
|
||||
w: object.Permission[1]?.Write,
|
||||
x: object.Permission[1]?.Exec
|
||||
},
|
||||
other: {
|
||||
r: object.Permission[2]?.Read,
|
||||
w: object.Permission[2]?.Write,
|
||||
x: object.Permission[2]?.Exec
|
||||
}
|
||||
},
|
||||
owner: object.Owner,
|
||||
group: object.Group,
|
||||
timestamps: {
|
||||
modified: new Date(object.TimeStamps.MTime),
|
||||
changed: new Date(object.TimeStamps.CTime),
|
||||
accessed: new Date(object.TimeStamps.ATime)
|
||||
},
|
||||
parent: object.Parent
|
||||
};
|
||||
|
||||
FsTable.set(object.Inode, node);
|
||||
}
|
||||
return FsTable;
|
||||
}
|
||||
|
||||
async function fetchFsJson(sig: string): Promise<any> {
|
||||
const signature: string | null = localStorage.getItem('signature');
|
||||
|
||||
if (signature !== sig || signature === null || localStorage.getItem('fs') === null) {
|
||||
const fs: JSON = await fetchFileSystem('/src/lib/assets/fs/fs.json');
|
||||
localStorage.setItem('signature', sig);
|
||||
localStorage.setItem('fs', JSON.stringify(fs));
|
||||
|
||||
console.info('FS fetched from file, new sinature:', sig);
|
||||
|
||||
return fs;
|
||||
} else {
|
||||
const fs: string | null = localStorage.getItem('fs');
|
||||
if (fs === null) throw new Error('FS in LocalStorage is null!');
|
||||
|
||||
console.info('FS fetched from localStorage with signature:', sig);
|
||||
|
||||
return JSON.parse(fs);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchFsSignature(path: string): Promise<any> {
|
||||
try {
|
||||
const response = await fetch(path);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.text();
|
||||
return data.slice(0, 36); //32 characters of uuid + 4 dashes
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch the file system signature', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchFileSystem(path: string): Promise<any> {
|
||||
const response = await fetch(path);
|
||||
if (!response.ok) throw new Error('Failed to fetch the file system json');
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function initTerminal(user: User, callbackInit: any): Promise<Terminal> {
|
||||
try {
|
||||
const sig = await fetchFsSignature('/src/lib/assets/fs/signature');
|
||||
const fsJson = await fetchFsJson(sig);
|
||||
const fs: Map<number, TreeNode> = jsonToNodeTable(fsJson);
|
||||
|
||||
const args: TermInitArgs = {
|
||||
bash: {
|
||||
user,
|
||||
fs
|
||||
}
|
||||
};
|
||||
|
||||
const terminal = new Terminal(args);
|
||||
terminal.registerCallbacks(callbackInit);
|
||||
|
||||
return terminal;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize terminal:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
initializing = false;
|
||||
}
|
||||
}
|
||||
40
src/lib/stores/terminal/stdio.ts
Executable file
40
src/lib/stores/terminal/stdio.ts
Executable file
@@ -0,0 +1,40 @@
|
||||
import { mount, unmount } from 'svelte';
|
||||
import Output from '../../../modules/terminal/Output.svelte';
|
||||
import { isInitializing } from './init.svelte';
|
||||
import type { PrintData } from './terminal';
|
||||
|
||||
interface OutputProps {
|
||||
path: string;
|
||||
output: any;
|
||||
cmd: string;
|
||||
}
|
||||
|
||||
const outputInstances = new Set<Output>();
|
||||
|
||||
function appendOutput(container: HTMLElement, props: OutputProps): Output | undefined {
|
||||
if (!container) return;
|
||||
const instance = mount(Output, {
|
||||
target: container,
|
||||
props
|
||||
});
|
||||
|
||||
outputInstances.add(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function print(e: HTMLElement, data: PrintData): void {
|
||||
if (isInitializing()) {
|
||||
console.error('Terminal is initializing! Skipping Print');
|
||||
return;
|
||||
}
|
||||
appendOutput(e, {
|
||||
path: data.path,
|
||||
output: data.output,
|
||||
cmd: data.cmd
|
||||
});
|
||||
}
|
||||
|
||||
export function clear(): void {
|
||||
console.log("outInstances", outputInstances);
|
||||
unmount(outputInstances);
|
||||
}
|
||||
135
src/lib/stores/terminal/terminal.ts
Executable file
135
src/lib/stores/terminal/terminal.ts
Executable file
@@ -0,0 +1,135 @@
|
||||
import { Bash, ExitCode, type BashInitArgs, type User } from '../bash/bash';
|
||||
import type { VirtualFS } from '../bash/fs';
|
||||
import type { CommandArgs } from '../bash/static';
|
||||
import { Char } from '../char';
|
||||
|
||||
export type TerminalMode = {};
|
||||
|
||||
export type TermInitArgs = {
|
||||
bash: BashInitArgs;
|
||||
};
|
||||
|
||||
export type ParsedInput = {
|
||||
command: string;
|
||||
args: CommandArgs;
|
||||
};
|
||||
|
||||
export type PrintData = {
|
||||
path: string;
|
||||
output: any; // TODO: Make this be any predefined format of outputs like ls, ls w/ flags and so on;
|
||||
cmd: string;
|
||||
};
|
||||
|
||||
export type PageCallbacks = {
|
||||
print: (data: PrintData) => void;
|
||||
clear: () => void;
|
||||
getWidth: () => number;
|
||||
getFontSize: () => number;
|
||||
};
|
||||
|
||||
export class Terminal {
|
||||
private bash: Bash;
|
||||
private callbacks: Partial<PageCallbacks> = {};
|
||||
|
||||
constructor(args: TermInitArgs) {
|
||||
args.bash.stdio = this;
|
||||
this.bash = new Bash(args.bash);
|
||||
}
|
||||
|
||||
private _parseInput(input: string): ParsedInput {
|
||||
let args: string[] = [];
|
||||
const result: ParsedInput = { command: '', args: { flags: [], args: [] } };
|
||||
let current: string = '';
|
||||
let inQuotes: boolean = false;
|
||||
let quoteChar: string = '';
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const char = input[i];
|
||||
|
||||
if ((char === '"' || char === "'") && !inQuotes) {
|
||||
inQuotes = true;
|
||||
quoteChar = char;
|
||||
continue;
|
||||
} else if (char === quoteChar && inQuotes) {
|
||||
inQuotes = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === ' ' && !inQuotes) {
|
||||
if (current === '') continue;
|
||||
|
||||
result.command === '' ? (result.command = current) : args.push(current);
|
||||
current = '';
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
if (current !== '') result.command === '' ? (result.command = current) : args.push(current);
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
let curr = args[i];
|
||||
console.log(curr);
|
||||
|
||||
if (curr.startsWith('--')) {
|
||||
curr = curr.replaceAll('--', '');
|
||||
if (curr.length === 0) continue;
|
||||
|
||||
result.args.flags.push(curr);
|
||||
} else if (curr.startsWith('-')) {
|
||||
curr = curr.replaceAll('-', '');
|
||||
if (curr.length === 0) continue;
|
||||
|
||||
for (let n = 0; n < curr.length; n++) {
|
||||
result.args.flags.push(curr[n]);
|
||||
}
|
||||
} else result.args.args.push(curr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
executeCommand(input: string): void {
|
||||
this.bash.updateHistory(input);
|
||||
const parsed: ParsedInput = this._parseInput(input);
|
||||
console.log(parsed, 'executeCommand output');
|
||||
this.bash.executeCommand(parsed.command, parsed.args);
|
||||
}
|
||||
|
||||
registerCallbacks(callbacks: PageCallbacks): void {
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
getUser(): User {
|
||||
return this.bash.getUser();
|
||||
}
|
||||
|
||||
getTerminalWidth(): number {
|
||||
const width = this.callbacks.getWidth?.();
|
||||
if(!width) { throw new Error('somehow width is undefined still after all the checks'); }
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
getFontSize(): number {
|
||||
const size = this.callbacks.getFontSize?.();
|
||||
if(!size) { throw new Error('somehow font size is undefined still after all the checks'); }
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
getCwd(): string {
|
||||
const fs: VirtualFS = this.bash.getFs();
|
||||
console.log(fs.getPathByInode(this.bash.getCwd()));
|
||||
return fs.formatPath(fs.getPathByInode(this.bash.getCwd()));
|
||||
}
|
||||
|
||||
//TODO: Later reimplement the backend helper methods
|
||||
/* userLogin(username: string, passwd: string): ExitCode {
|
||||
return this.bash.userLogin(username, passwd);
|
||||
} */
|
||||
|
||||
PrintOutput(data: PrintData) {
|
||||
this.callbacks.print?.(data);
|
||||
}
|
||||
}
|
||||
17
src/lib/stores/theme.ts
Executable file
17
src/lib/stores/theme.ts
Executable file
@@ -0,0 +1,17 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
function getInitalTheme(): string {
|
||||
if (typeof window === 'undefined') return 'dark';
|
||||
|
||||
const savedTheme: string | null = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark' || savedTheme === 'light') return savedTheme;
|
||||
|
||||
const sysPrefTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
return sysPrefTheme ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
export const theme = writable(getInitalTheme());
|
||||
|
||||
theme.subscribe((value) => {
|
||||
if (typeof window !== 'undefined') localStorage.setItem('theme', value);
|
||||
});
|
||||
55
src/modules/Footer.svelte
Executable file
55
src/modules/Footer.svelte
Executable file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import { Languages, SunMoon } from '@lucide/svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import plFlag from '$lib/assets/plFlag.svg';
|
||||
import enFlag from '$lib/assets/enFlag.svg';
|
||||
</script>
|
||||
|
||||
<div
|
||||
id="footer"
|
||||
class="fixed bottom-0 flex w-max flex-row-reverse items-center justify-between p-2"
|
||||
>
|
||||
<div class="footer-child flex flex-row-reverse content-start items-center" id="footer-lang-child">
|
||||
<button
|
||||
type="button"
|
||||
id="lang-switch"
|
||||
class="button rounded-lg bg-bg text-primary hover:text-primary-hover"
|
||||
>
|
||||
<Languages class="m-2.5 size-10" />
|
||||
</button>
|
||||
<div id="lang-wrapper" class="">
|
||||
<div id="pl" class="lang visible">
|
||||
<img class="flags" src={plFlag} alt="PL" height="30" width="30" />
|
||||
</div>
|
||||
<div id="en" class="lang">
|
||||
<img class="flags" src={enFlag} alt="EN" height="30" width="30" />
|
||||
</div>
|
||||
<!-- <div id="ja" class="lang"><img class="flags" src="/images/japan-svgrepo-com.svg" alt="JA" height="30" width="30"/></div>
|
||||
<div id="de" class="lang"><img class="flags" src="/images/germany-svgrepo-com.svg" alt="DE" height="30" width="30"/></div>
|
||||
<div id="fr" class="lang"><img class="flags" src="/images/france-svgrepo-com.svg" alt="FR" height="30" width="30"/></div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="footer-child flex flex-row content-start items-center bg-bg text-primary hover:text-primary-hover"
|
||||
id="footer-theme-child"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
id="theme-switch"
|
||||
class="button rounded-lg"
|
||||
onclick={() => {
|
||||
$theme = $theme === 'dark' ? 'light' : 'dark';
|
||||
}}
|
||||
>
|
||||
<SunMoon class="m-2.5 size-10" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.button {
|
||||
transition: var(--transition-standard);
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
57
src/modules/Loading.svelte
Executable file
57
src/modules/Loading.svelte
Executable file
@@ -0,0 +1,57 @@
|
||||
<div
|
||||
id="loader-master"
|
||||
class=" light:bg-dots-bg-2-light pointer-events-none visible absolute z-49 size-full bg-bg-dark select-none"
|
||||
>
|
||||
<div id="loader" class="tran absolute top-1/2 left-1/2 z-50 -translate-x-1/2 -translate-y-1/2">
|
||||
<div id="bar-wrapper" class="flex h-full flex-row items-center gap-2">
|
||||
<div
|
||||
class="bar h-10 w-2 origin-center rounded-[3px] bg-loader-primary-dark light:bg-loader-primary-light"
|
||||
></div>
|
||||
<div
|
||||
class="bar h-10 w-2 origin-center rounded-[3px] bg-loader-primary-dark light:bg-loader-primary-light"
|
||||
></div>
|
||||
<div
|
||||
class="bar h-10 w-2 origin-center rounded-[3px] bg-loader-primary-dark light:bg-loader-primary-light"
|
||||
></div>
|
||||
<div
|
||||
class="bar h-10 w-2 origin-center rounded-[3px] bg-loader-primary-dark light:bg-loader-primary-light"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#loader-master {
|
||||
transition: all 0.3s cubic-bezier(0.41, 0.68, 0.45, 0.96);
|
||||
}
|
||||
|
||||
.bar {
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.bar:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
.bar:nth-child(2) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.bar:nth-child(3) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.bar:nth-child(4) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
30% {
|
||||
transform: scaleY(0.35);
|
||||
background-color: oklch(0.8 0.15 292);
|
||||
}
|
||||
60% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
4
src/modules/Panel.svelte
Executable file
4
src/modules/Panel.svelte
Executable file
@@ -0,0 +1,4 @@
|
||||
<section>
|
||||
<section></section>
|
||||
<section></section>
|
||||
</section>
|
||||
110
src/modules/Settings.svelte
Executable file
110
src/modules/Settings.svelte
Executable file
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { Languages, Settings2, SunMoon } from '@lucide/svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import plFlag from '$lib/assets/plFlag.svg';
|
||||
import enFlag from '$lib/assets/enFlag.svg';
|
||||
import deFlag from '$lib/assets/deFlag.svg';
|
||||
import frFlag from '$lib/assets/frFlag.svg';
|
||||
import jaFlag from '$lib/assets/jaFlag.svg';
|
||||
|
||||
function toggleSettings() {
|
||||
const settingsMenu = window.document.getElementById('settings-menu');
|
||||
const langsMenu = window.document.getElementById('langs-menu');
|
||||
langsMenu?.classList.add('hide');
|
||||
settingsMenu?.classList.toggle('hide');
|
||||
}
|
||||
|
||||
function toggleLangs() {
|
||||
const langsMenu = window.document.getElementById('langs-menu');
|
||||
langsMenu?.classList.toggle('hide');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
id="options"
|
||||
class="absolute top-0 left-0 isolate mx-4 rounded-b-xl bg-bg-dark light:bg-bg-light"
|
||||
>
|
||||
<section class="hide grid" id="settings-menu">
|
||||
<div class="flex flex-col items-center justify-center gap-4 overflow-hidden">
|
||||
<div class=" mt-4 flex items-center justify-center rounded-lg">
|
||||
<button
|
||||
class=" rounded-lg bg-bg-lighter-dark p-1.5 text-text-dark shadow-button duration-75 hover:text-primary-hover-dark active:translate-y-0.5 light:bg-bg-lighter-light light:text-text-muted-light light:hover:text-primary-hover-light"
|
||||
id="theme-btn"
|
||||
onclick={() => {
|
||||
$theme = $theme === 'dark' ? 'light' : 'dark';
|
||||
}}
|
||||
>
|
||||
<SunMoon strokeWidth={1} size={32} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col content-center items-center">
|
||||
<button
|
||||
class="z-3 mb-4 rounded-lg bg-bg-lighter-dark p-1.5 text-text-dark duration-75 hover:text-primary-hover-dark active:translate-y-0.5 light:bg-bg-lighter-light light:text-text-muted-light light:hover:text-primary-hover-light"
|
||||
id="langs-btn"
|
||||
onclick={toggleLangs}
|
||||
>
|
||||
<Languages strokeWidth={1} size={32} />
|
||||
</button>
|
||||
<form id="langs-menu" class="hide grid">
|
||||
<div class="z-2 mb-4 flex flex-col items-center gap-2 overflow-hidden">
|
||||
<label
|
||||
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
||||
>
|
||||
<input name="langs" type="radio" class="lang hidden" id="pl" autocomplete="off" />
|
||||
<img class="flags mx-2" src={plFlag} alt="PL" height="26" width="26" />
|
||||
</label>
|
||||
<label
|
||||
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
||||
>
|
||||
<input name="langs" type="radio" class="lang hidden" id="en" autocomplete="off" />
|
||||
<img class="flags mx-2" src={enFlag} alt="EN" height="26" width="26" />
|
||||
</label>
|
||||
<label
|
||||
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
||||
>
|
||||
<input name="langs" type="radio" class="lang hidden" id="en" autocomplete="off" />
|
||||
<img class="flags mx-2" src={deFlag} alt="DE" height="26" width="26" />
|
||||
</label>
|
||||
<label
|
||||
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
||||
>
|
||||
<input name="langs" type="radio" class="lang hidden" id="en" autocomplete="off" />
|
||||
<img class="flags mx-2" src={jaFlag} alt="JA" height="26" width="26" />
|
||||
</label>
|
||||
<label
|
||||
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
||||
>
|
||||
<input name="langs" type="radio" class="lang hidden" id="en" autocomplete="off" />
|
||||
<img class="flags mx-2" src={frFlag} alt="FR" height="26" width="26" />
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<button
|
||||
class="rounded-b-xl border-t bg-bg-light-dark p-2 text-text-dark shadow-button hover:text-primary-dark light:bg-bg-lighter-light light:text-text-muted-light light:hover:text-primary-light"
|
||||
id="settings-btn"
|
||||
onclick={toggleSettings}
|
||||
>
|
||||
<Settings2 strokeWidth={1} size={38} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#langs-menu {
|
||||
transition: 0.2s ease-in;
|
||||
grid-template-rows: 1fr;
|
||||
&.hide {
|
||||
grid-template-rows: 0fr;
|
||||
}
|
||||
}
|
||||
|
||||
#settings-menu {
|
||||
transition: 0.2s ease-in;
|
||||
grid-template-rows: 1fr;
|
||||
&.hide {
|
||||
grid-template-rows: 0fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
169
src/modules/Terminal.svelte
Executable file
169
src/modules/Terminal.svelte
Executable file
@@ -0,0 +1,169 @@
|
||||
<script lang="ts">
|
||||
import type { User } from '$lib/stores/bash/bash';
|
||||
import { Terminal, type PrintData } from '$lib/stores/terminal/terminal';
|
||||
import { onMount } from 'svelte';
|
||||
import Input from './terminal/Input.svelte';
|
||||
import { initTerminal, isInitializing } from '$lib/stores/terminal/init.svelte';
|
||||
import { clear, print } from '$lib/stores/terminal/stdio';
|
||||
|
||||
const clearTerminal = (): void => clear();
|
||||
|
||||
const printOutput = (e: HTMLElement, d: PrintData): void => print(e, d);
|
||||
|
||||
function updateTerminal() {
|
||||
username = terminal!.getUser().username;
|
||||
cwd = terminal!.getCwd();
|
||||
}
|
||||
|
||||
function getWidth() {
|
||||
const e = document.getElementById('cout');
|
||||
if(!e){
|
||||
throw new Error('cant get width of the teminal element. Its null');
|
||||
}
|
||||
//gets an int from padding property value (which is a string) by cutting last 2 letters "px" and parsing to int
|
||||
const padding: number = parseInt(window.getComputedStyle(e, null).getPropertyValue('padding').slice(0, -2));
|
||||
return e.clientWidth - (padding * 2);
|
||||
}
|
||||
|
||||
function getFontSize() {
|
||||
return 10;
|
||||
/* const e = document.getElementById('cout');
|
||||
if(!e) {
|
||||
throw new Error('cant get font size of the terminal element. its null');
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.font = `${window.getComputedStyle(e, null).getPropertyValue('font-size')} 'JetBrains Mono', monospace;`;
|
||||
return ctx.measureText(' ').width; */
|
||||
}
|
||||
|
||||
function handleInput(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'Enter': {
|
||||
terminal.executeCommand(inputValue);
|
||||
updateTerminal();
|
||||
break;
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
//TODO: Move cursor visually
|
||||
}
|
||||
case 'ArrowLeft': {
|
||||
//TODO: Move cursor visually
|
||||
}
|
||||
case 'ArrowUp': {
|
||||
//TODO: Make a traverse history function with up/down args
|
||||
}
|
||||
case 'ArrowDown': {
|
||||
//TODO: Make a traverse history function with up/down args
|
||||
}
|
||||
}
|
||||
|
||||
const elem = document.getElementById('cout');
|
||||
if(!elem){
|
||||
throw new Error('cant scroll to bottom, element is null');
|
||||
}
|
||||
|
||||
elem.scrollTop = elem.scrollHeight;
|
||||
}
|
||||
|
||||
//Callback initializer
|
||||
const callbackInit = {
|
||||
print: (data: any) => {
|
||||
const e = document.getElementById('outputWrapper');
|
||||
if (!e) return;
|
||||
printOutput(e, data);
|
||||
},
|
||||
clear: clear,
|
||||
getWidth: getWidth,
|
||||
getFontSize: getFontSize
|
||||
};
|
||||
|
||||
//Test user with basic data so the bash can run
|
||||
let testUser: User = {
|
||||
username: 'kamil',
|
||||
passwd: '123',
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
home: '/home/kamil',
|
||||
history: []
|
||||
};
|
||||
|
||||
//Empty terminal variable where the terminal class instance will be stored
|
||||
let terminal: Terminal;
|
||||
let username: string = $state(testUser.username);
|
||||
let cwd: string = $state(testUser.home);
|
||||
|
||||
let inputValue = $state<string>('');
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
terminal = await initTerminal(testUser, callbackInit);
|
||||
updateTerminal();
|
||||
} catch (error) {
|
||||
console.error('onMount trycatch failed', error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<label for="input" onkeydowncapture={(e) => handleInput(e)} class="w-11/12">
|
||||
<div id="terminal" class="terminal-window shadow-() h-full w-full rounded-md shadow-bg">
|
||||
<div
|
||||
class="terminal-bar flex h-9 w-full flex-row items-center rounded-t-md bg-bg-dark text-center font-terminal text-sm font-bold text-primary-dark light:bg-bg-dark-light light:text-primary-light"
|
||||
>
|
||||
<div class="dots-wrapper mx-2.5 flex h-full flex-row items-center justify-center gap-2.5">
|
||||
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
|
||||
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
|
||||
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
|
||||
</div>
|
||||
<div class=" flex mr-2 grow">
|
||||
<h5>{username}</h5>
|
||||
<!-- prettier-ignore -->
|
||||
<h5 class=" mr-2">@terminal: </h5>
|
||||
<h5>{cwd}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="inner-content scroll-hidden h-7/8 origin-top overflow-y-auto rounded-b-md bg-bg-light-dark p-4 text-text-dark shadow-subtle light:bg-bg-lighter-light light:text-text-light"
|
||||
id="cout"
|
||||
>
|
||||
<div id="outputWrapper"></div>
|
||||
<Input {cwd} bind:inputValue />
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<style>
|
||||
* {
|
||||
transition: var(--transition-standard);
|
||||
}
|
||||
.dots-wrapper {
|
||||
& > button {
|
||||
border: none;
|
||||
&:hover {
|
||||
transform: translateY(-0.1rem);
|
||||
}
|
||||
&:active {
|
||||
transform: translate(0);
|
||||
}
|
||||
&:nth-child(1) {
|
||||
background-color: rgb(255, 0, 0);
|
||||
&:active {
|
||||
background-color: rgb(255, 100, 100);
|
||||
}
|
||||
}
|
||||
&:nth-child(2) {
|
||||
background-color: rgb(255, 165, 0);
|
||||
&:active {
|
||||
background-color: rgb(255, 215, 50);
|
||||
}
|
||||
}
|
||||
&:nth-child(3) {
|
||||
background-color: rgb(50, 205, 50);
|
||||
&:active {
|
||||
background-color: rgb(100, 255, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
36
src/modules/panel/LangModule.svelte
Executable file
36
src/modules/panel/LangModule.svelte
Executable file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { SquarePen } from '@lucide/svelte';
|
||||
|
||||
let {
|
||||
langName,
|
||||
icon,
|
||||
checked = false
|
||||
}: { langName: string; icon: string; checked?: boolean } = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class=" flex items-center text-text-dark saturate-50 has-checked:saturate-100 light:text-text-light"
|
||||
>
|
||||
<div class=" flex w-full flex-col justify-center gap-1 rounded-lg bg-bg-mid-dark">
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="m-2 mr-1 rounded-md bg-bg-lighter-dark p-1 shadow-subtle hover:text-primary-hover-dark active:text-primary-dark"
|
||||
>
|
||||
<SquarePen size={24} strokeWidth={1} />
|
||||
</button>
|
||||
<img alt="" width="32" height="32" src={icon} />
|
||||
<h5 class=" mr-2 font-primary font-bold">{langName}</h5>
|
||||
<label
|
||||
for={langName}
|
||||
class="relative m-2 ml-auto block h-5 w-10 cursor-pointer rounded-full bg-bg-lighter-dark shadow-subtle"
|
||||
>
|
||||
<input type="checkbox" {checked} id={langName} class=" peer sr-only" />
|
||||
<span
|
||||
class=" absolute top-0.5 left-0.5 h-4/5 w-2/5 rounded-full bg-bg-dark peer-checked:left-5.5 peer-checked:bg-primary-dark"
|
||||
></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" flex items-center justify-center rounded-r-lg bg-bg-dark"></div>
|
||||
</div>
|
||||
26
src/modules/panel/ProjectModule.svelte
Executable file
26
src/modules/panel/ProjectModule.svelte
Executable file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { ChevronRight, Power, SquarePen } from '@lucide/svelte';
|
||||
|
||||
let { projectName, checked = true }: { projectName: string; checked?: boolean } = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class=" flex items-center text-text-dark saturate-50 has-checked:saturate-100 light:text-text-light"
|
||||
>
|
||||
<div class=" flex w-full flex-col justify-center gap-1 rounded-lg bg-bg-mid-dark">
|
||||
<h5 class=" m-4 font-primary text-xl font-bold">{projectName}</h5>
|
||||
<div class="flex items-center gap-2">
|
||||
<label
|
||||
for={projectName}
|
||||
class="relative m-2 ml-auto block h-5 w-10 cursor-pointer rounded-full bg-bg-lighter-dark shadow-subtle"
|
||||
>
|
||||
<input type="checkbox" {checked} id={projectName} class=" peer sr-only" />
|
||||
<span
|
||||
class=" absolute top-0.5 left-0.5 h-4/5 w-2/5 rounded-full bg-bg-dark peer-checked:left-5.5 peer-checked:bg-primary-dark"
|
||||
></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" flex items-center justify-center rounded-r-lg bg-bg-dark"></div>
|
||||
</div>
|
||||
29
src/modules/panel/SidebarButton.svelte
Executable file
29
src/modules/panel/SidebarButton.svelte
Executable file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
children,
|
||||
path = '/',
|
||||
checked = false
|
||||
}: { children: Snippet; path?: string; checked?: boolean } = $props();
|
||||
|
||||
onMount(() => {
|
||||
if (checked) goto(path);
|
||||
});
|
||||
</script>
|
||||
|
||||
<label
|
||||
class="m-0 flex size-12 items-center justify-center rounded-xl bg-bg-lighter-dark shadow-subtle hover:text-primary-hover-dark has-checked:text-primary-dark has-checked:hover:text-primary-hover-dark light:bg-bg-light-light light:hover:text-primary-hover-light light:has-checked:text-primary-light light:has-checked:hover:text-primary-hover-light"
|
||||
>
|
||||
<input
|
||||
onclick={() => goto(path)}
|
||||
name="langs"
|
||||
type="radio"
|
||||
class="hidden"
|
||||
id="pl"
|
||||
autocomplete="off"
|
||||
{checked}
|
||||
/>
|
||||
{@render children()}
|
||||
</label>
|
||||
37
src/modules/terminal/Input.svelte
Executable file
37
src/modules/terminal/Input.svelte
Executable file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
let {
|
||||
inputValue = $bindable(),
|
||||
isFocused,
|
||||
cwd
|
||||
}: { inputValue: string; isFocused?: boolean; cwd: string } = $props();
|
||||
|
||||
function handleInput(event: Event) {
|
||||
inputValue = (event.target as HTMLInputElement).value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class=" relative">
|
||||
<input
|
||||
bind:value={inputValue}
|
||||
onkeydown={(e) => e.key === 'Enter' && (inputValue = '')}
|
||||
oninput={handleInput}
|
||||
onfocus={() => (isFocused = true)}
|
||||
onblur={() => (isFocused = false)}
|
||||
type="text"
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
class=" pointer-events-none absolute left-0 m-0 w-0 border-none p-0 opacity-0"
|
||||
id="input"
|
||||
/>
|
||||
<p class="cwd" id="cwd">{cwd}</p>
|
||||
<div class="w flex-column flex flex-row flex-wrap font-terminal">
|
||||
<span class="pointer pr-2">$</span>
|
||||
<!-- prettier-ignore -->
|
||||
<div style="white-space: preserve;" class=" relative wrap-break-word">{inputValue}</div>
|
||||
<span id="cursor" class={isFocused ? 'animate-cursor-blink' : ''}>_</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
20
src/modules/terminal/Output.svelte
Executable file
20
src/modules/terminal/Output.svelte
Executable file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
let { path, output, cmd }: { path: string; output: any; cmd: string } = $props(); //TODO: change any to matching
|
||||
</script>
|
||||
|
||||
<p class="cwd" id="cwd">{path}</p>
|
||||
<div class="pointer-wrapper mr-4 mb-2.5 flex flex-col justify-center font-terminal">
|
||||
<div class=" flex flex-row items-center">
|
||||
<span class=" pointer self-start pr-2">$</span>
|
||||
<p>{cmd}</p>
|
||||
</div>
|
||||
<div style="white-space: pre;" class=" relative">
|
||||
{#if typeof output === 'string'}
|
||||
{output}
|
||||
{:else if output instanceof Element}
|
||||
{@html output.outerHTML}
|
||||
{:else}
|
||||
{output}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
54
src/routes/+layout.svelte
Executable file
54
src/routes/+layout.svelte
Executable file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
|
||||
let { children }: { children: Snippet } = $props();
|
||||
|
||||
let bg1: string | null;
|
||||
let bg2: string | null;
|
||||
|
||||
onMount(() => {
|
||||
const style = window.getComputedStyle(document.documentElement);
|
||||
|
||||
bg1 = style.getPropertyValue('--dots-bg-1');
|
||||
bg2 = style.getPropertyValue('--dots-bg-2');
|
||||
|
||||
if (bg2 && bg1) {
|
||||
window.CSS.registerProperty({
|
||||
name: '--dots-bg-2',
|
||||
syntax: '<color>',
|
||||
inherits: true,
|
||||
initialValue: bg2
|
||||
});
|
||||
|
||||
window.CSS.registerProperty({
|
||||
name: '--dots-bg-1',
|
||||
syntax: '<color>',
|
||||
inherits: true,
|
||||
initialValue: bg1
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
|
||||
<main
|
||||
class="text-text m-auto flex min-h-dvh w-full flex-col items-center justify-center overflow-hidden bg-dots-bg bg-size-[20px_20px]"
|
||||
class:dark={$theme === 'dark'}
|
||||
class:light={$theme === 'light'}
|
||||
>
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
transition:
|
||||
--dots-bg-2 0.2s ease-in,
|
||||
--dots-bg-1 0.2s ease-in;
|
||||
}
|
||||
</style>
|
||||
14
src/routes/+page.svelte
Executable file
14
src/routes/+page.svelte
Executable file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import TerminalModule from '../modules/Terminal.svelte';
|
||||
import Settings from '../modules/Settings.svelte';
|
||||
import Loading from '../modules/Loading.svelte';
|
||||
import { isInitializing } from '$lib/stores/terminal/init.svelte';
|
||||
</script>
|
||||
|
||||
<Settings></Settings>
|
||||
<div class="h-dvh w-full p-24 flex justify-center">
|
||||
<TerminalModule />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
0
src/routes/errors/404/+page.svelte
Executable file
0
src/routes/errors/404/+page.svelte
Executable file
13
src/routes/page.svelte.spec.ts
Executable file
13
src/routes/page.svelte.spec.ts
Executable file
@@ -0,0 +1,13 @@
|
||||
import { page } from '@vitest/browser/context';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import Page from './+page.svelte';
|
||||
|
||||
describe('/+page.svelte', () => {
|
||||
it('should render h1', async () => {
|
||||
render(Page);
|
||||
|
||||
const heading = page.getByRole('heading', { level: 1 });
|
||||
await expect.element(heading).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
50
src/routes/panel/+layout.svelte
Executable file
50
src/routes/panel/+layout.svelte
Executable file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import '../../app.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { BookA, ChartLine, ChartNoAxesColumn, FolderArchive, Plus } from '@lucide/svelte';
|
||||
import SidebarButton from '../../modules/panel/SidebarButton.svelte';
|
||||
|
||||
let { children }: { children: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex h-dvh w-full flex-row text-text-dark light:text-text-light">
|
||||
<div class="flex h-full w-fit flex-col items-center bg-bg-dark light:bg-bg-light">
|
||||
<img class="m-3 mb-8" alt="Logo" src={favicon} height="32" width="32" />
|
||||
<form class="flex h-full w-fit flex-col items-center gap-3">
|
||||
<label
|
||||
class="m-0 mb-4 flex size-12 items-center justify-center rounded-xl bg-bg-lighter-dark shadow-subtle hover:text-primary-hover-dark has-checked:text-primary-dark has-checked:hover:text-primary-hover-dark light:bg-bg-light-light light:hover:text-primary-hover-light light:has-checked:text-primary-light light:has-checked:hover:text-primary-hover-light"
|
||||
>
|
||||
<input name="langs" type="radio" class="hidden" id="pl" autocomplete="off" />
|
||||
<Plus strokeWidth={1} />
|
||||
</label>
|
||||
<SidebarButton checked path="/panel/projects">
|
||||
<FolderArchive strokeWidth={1} />
|
||||
</SidebarButton>
|
||||
<SidebarButton path="/panel/langs">
|
||||
<BookA strokeWidth={1} />
|
||||
</SidebarButton>
|
||||
<SidebarButton path="/panel/metrics">
|
||||
<ChartLine strokeWidth={1} />
|
||||
</SidebarButton>
|
||||
<SidebarButton path="/panel/stats">
|
||||
<ChartNoAxesColumn strokeWidth={1} />
|
||||
</SidebarButton>
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex w-full grow">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
main {
|
||||
transition:
|
||||
--dots-bg-2 0.2s ease-in,
|
||||
--dots-bg-1 0.2s ease-in;
|
||||
}
|
||||
</style>
|
||||
0
src/routes/panel/+page.svelte
Executable file
0
src/routes/panel/+page.svelte
Executable file
19
src/routes/panel/langs/+page.svelte
Executable file
19
src/routes/panel/langs/+page.svelte
Executable file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import LangModule from '../../../modules/panel/LangModule.svelte';
|
||||
import plFlag from '$lib/assets/plFlag.svg';
|
||||
import enFlag from '$lib/assets/enFlag.svg';
|
||||
import deFlag from '$lib/assets/deFlag.svg';
|
||||
import frFlag from '$lib/assets/frFlag.svg';
|
||||
import jaFlag from '$lib/assets/jaFlag.svg';
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<form name="langs" class="m-10 flex flex-col gap-4">
|
||||
<LangModule checked langName="Polish" icon={plFlag} />
|
||||
<LangModule checked langName="English" icon={enFlag} />
|
||||
<LangModule checked langName="German" icon={deFlag} />
|
||||
<LangModule langName="Japanese" icon={jaFlag} />
|
||||
<LangModule langName="French" icon={frFlag} />
|
||||
</form>
|
||||
<div></div>
|
||||
</div>
|
||||
1
src/routes/panel/metrics/+page.svelte
Executable file
1
src/routes/panel/metrics/+page.svelte
Executable file
@@ -0,0 +1 @@
|
||||
METRICS
|
||||
7
src/routes/panel/projects/+page.svelte
Executable file
7
src/routes/panel/projects/+page.svelte
Executable file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import ProjectModule from '../../../modules/panel/ProjectModule.svelte';
|
||||
</script>
|
||||
|
||||
<div class=" m-10">
|
||||
<ProjectModule projectName="ProjectName" />
|
||||
</div>
|
||||
1
src/routes/panel/stats/+page.svelte
Executable file
1
src/routes/panel/stats/+page.svelte
Executable file
@@ -0,0 +1 @@
|
||||
STATS
|
||||
0
src/routes/test/+page.svelte
Executable file
0
src/routes/test/+page.svelte
Executable file
26
src/style/animations.css
Executable file
26
src/style/animations.css
Executable file
@@ -0,0 +1,26 @@
|
||||
@theme {
|
||||
--animate-loading: loading 1s ease-in-out infinite;
|
||||
--animate-cursor-blink: blink 1s step-end infinite;
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
30% {
|
||||
transform: scaleY(0.35);
|
||||
background-color: var(--color-loader-primary-hl);
|
||||
}
|
||||
60% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
955
src/style/fonts.css
Executable file
955
src/style/fonts.css
Executable file
@@ -0,0 +1,955 @@
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible Mono';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/atkinsonhyperlegiblemono/v8/tssPAoFBci4C4gvhPXrt3wjT1MqSzhA4t7IIcncBiwKonlKh6PW-UyGM1JTKSRMW8qj-lw.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible Mono';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/atkinsonhyperlegiblemono/v8/tssPAoFBci4C4gvhPXrt3wjT1MqSzhA4t7IIcncBiwKonlKh6PW-UyGM1JTKSRMY8qg.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible Mono';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/atkinsonhyperlegiblemono/v8/tssNAoFBci4C4gvhPXrt3wjT1MqSzhA4t7IIcncBiyihrK15gZ4k_SaZnNCSBCMa6qw.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible Mono';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/atkinsonhyperlegiblemono/v8/tssNAoFBci4C4gvhPXrt3wjT1MqSzhA4t7IIcncBiyihrK15gZ4k_SaZnNCSCiMa.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Bungee Hairline';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/bungeehairline/v26/snfys0G548t04270a_ljTLUVrv-LaBecc5Y.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301,
|
||||
U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Bungee Hairline';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/bungeehairline/v26/snfys0G548t04270a_ljTLUVrv-LaRecc5Y.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Bungee Hairline';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/bungeehairline/v26/snfys0G548t04270a_ljTLUVrv-LZxec.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Doto';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/doto/v3/t5t6IRMbNJ6TQG7Il_EKPqP9zTkn6IuPWhojrg.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Doto';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/doto/v3/t5t6IRMbNJ6TQG7Il_EKPqP9zTkn6IuBWho.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Just Me Again Down Here';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/justmeagaindownhere/v25/MwQmbgXtz-Wc6RUEGNMc0QpRrfUh2hSdBBMoAtwOtKHAcw.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Just Me Again Down Here';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/justmeagaindownhere/v25/MwQmbgXtz-Wc6RUEGNMc0QpRrfUh2hSdBBMoAtwAtKE.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dNIFZifjKcF5UAWdDRYERMSHK_IwU.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301,
|
||||
U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dNIFZifjKcF5UAWdDRYERMSXK_IwU.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dNIFZifjKcF5UAWdDRYERMR3K_.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dSIFZifjKcF5UAWdDRYERE_FeqEySRV3U.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301,
|
||||
U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dSIFZifjKcF5UAWdDRYERE_FeqEiSRV3U.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dSIFZifjKcF5UAWdDRYERE_FeqHCSR.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dPIFZifjKcF5UAWdDRYE58RWq7.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301,
|
||||
U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dPIFZifjKcF5UAWdDRYE98RWq7.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dPIFZifjKcF5UAWdDRYEF8RQ.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dMIFZifjKcF5UAWdDRaPpZUFqaHjyV.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301,
|
||||
U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dMIFZifjKcF5UAWdDRaPpZUFuaHjyV.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/spacemono/v17/i7dMIFZifjKcF5UAWdDRaPpZUFWaHg.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible Mono';
|
||||
font-style: italic;
|
||||
font-weight: 200 800;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/atkinsonhyperlegiblemono/v8/tss6AoFBci4C4gvhPXrt3wjT1MqSzhA4t7IIcncBiwKotFCJGR0i.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible Mono';
|
||||
font-style: italic;
|
||||
font-weight: 200 800;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/atkinsonhyperlegiblemono/v8/tss6AoFBci4C4gvhPXrt3wjT1MqSzhA4t7IIcncBiwKotF6JGQ.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/quicksand/v37/6xKtdSZaM9iE8KbpRA_hJFQNcOM.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301,
|
||||
U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/quicksand/v37/6xKtdSZaM9iE8KbpRA_hJVQNcOM.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/quicksand/v37/6xKtdSZaM9iE8KbpRA_hK1QN.woff2) format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Silkscreen';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/silkscreen/v6/m8JXjfVPf62XiF7kO-i9YL1la1OD.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Silkscreen';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/silkscreen/v6/m8JXjfVPf62XiF7kO-i9YLNlaw.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Silkscreen';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/silkscreen/v6/m8JUjfVPf62XiF7kO-i9aAhAfmKi2Oud.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Silkscreen';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/silkscreen/v6/m8JUjfVPf62XiF7kO-i9aAhAfmyi2A.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Smooch Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/smoochsans/v15/c4mk1n5uGsXss2LJh1QH6Zd13KeHWA.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301,
|
||||
U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Smooch Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/smoochsans/v15/c4mk1n5uGsXss2LJh1QH6Zd03KeHWA.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Smooch Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/smoochsans/v15/c4mk1n5uGsXss2LJh1QH6Zd63Kc.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.3.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+fa10, U+fa12-fa6d, U+fb00-fb04, U+fe10-fe19, U+fe30-fe42, U+fe44-fe52, U+fe54-fe66,
|
||||
U+fe68-fe6b, U+ff02, U+ff04, U+ff07, U+ff51, U+ff5b, U+ff5d, U+ff5f-ff60, U+ff66, U+ff69,
|
||||
U+ff87, U+ffa1-ffbe, U+ffc2-ffc7, U+ffca-ffcf, U+ffd2-ffd6;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.54.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+3028-303f, U+3094-3096, U+309f-30a0, U+30ee, U+30f7-30fa, U+30ff, U+3105-312f, U+3131-3163,
|
||||
U+3165-318e, U+3190-31bb, U+31c0-31c7;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.58.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+2105, U+2109-210a, U+210f, U+2116, U+2121, U+2126-2127, U+212b, U+212e, U+2135, U+213b,
|
||||
U+2194-2199, U+21b8-21b9, U+21c4-21c6, U+21cb-21cc, U+21d0, U+21e6-21e9, U+21f5, U+2202-2203,
|
||||
U+2205-2206, U+2208-220b, U+220f, U+2211, U+2213, U+2215, U+221a, U+221d, U+2220, U+2223,
|
||||
U+2225-2226, U+2228, U+222a-222e, U+2234-2237, U+223d, U+2243, U+2245, U+2248, U+224c, U+2260,
|
||||
U+2262, U+2264-2265, U+226e-226f, U+2272-2273, U+2276-2277, U+2283-2287, U+228a-228b,
|
||||
U+2295-2299, U+22a0, U+22a5, U+22bf, U+22da-22db, U+22ef, U+2305-2307, U+2318, U+2329-232a,
|
||||
U+23b0-23b1, U+23be-23cc, U+23ce, U+23da-23db, U+2423, U+2469-24d0;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.59.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+a1-a4, U+a6-a7, U+aa, U+ac-ad, U+b5-b6, U+b8-ba, U+bc-c8, U+ca-cc, U+ce-d5, U+d9-db, U+dd-df,
|
||||
U+e6, U+ee, U+f0, U+f5, U+f7, U+f9, U+fb, U+fe-102, U+110-113, U+11a-11b, U+128-12b, U+143-144,
|
||||
U+147-148, U+14c, U+14e-14f, U+152-153, U+168-16d, U+192, U+1a0-1a1, U+1af, U+1cd-1dc,
|
||||
U+1f8-1f9, U+251, U+261, U+2bb, U+2c7, U+2c9, U+2ea-2eb, U+304, U+307, U+30c, U+1e3e-1e3f,
|
||||
U+1ea0-1ebe, U+1ec0-1ec6, U+1ec8-1ef9, U+2011-2012, U+2016, U+2018-201a, U+201e, U+2021, U+2030,
|
||||
U+2033, U+2035, U+2042, U+2047, U+2051, U+2074, U+20a9, U+20ab-20ac, U+20dd-20de, U+2100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.61.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+a8, U+2032, U+2261, U+2282, U+3090, U+30f1, U+339c, U+535c, U+53d9, U+56a2, U+56c1, U+5806,
|
||||
U+589f, U+59d0, U+5a7f, U+60e0, U+639f, U+65af, U+68fa, U+69ae, U+6d1b, U+6ef2, U+71fb, U+725d,
|
||||
U+7262, U+75bc, U+7768, U+7940, U+79bf, U+7bed, U+7d68, U+7dfb, U+814b, U+8207, U+83e9, U+8494,
|
||||
U+8526, U+8568, U+85ea, U+86d9, U+87ba, U+8861, U+887f, U+8fe6, U+9059, U+9061, U+916a, U+976d,
|
||||
U+97ad, U+9ece;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.65.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+b1, U+309b, U+4e5e, U+51f1, U+5506, U+55c5, U+58cc, U+59d1, U+5c51, U+5ef7, U+6284, U+62d7,
|
||||
U+6689, U+673d, U+6a2b, U+6a8e, U+6a9c, U+6d63, U+6dd1, U+70b8, U+7235, U+72db, U+72f8, U+7560,
|
||||
U+7c9b, U+7ce7, U+7e1e, U+80af, U+82eb, U+8463, U+8499, U+85dd, U+86ee, U+8a60, U+8a6e, U+8c79,
|
||||
U+8e87, U+8e8a, U+8f5f, U+9010, U+918d, U+9190, U+965b, U+97fb, U+9ab8, U+9bad, U+9d3b, U+9d5c,
|
||||
U+9dfa, U+9e93;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.70.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+266b, U+3006, U+5176, U+5197, U+51a8, U+51c6, U+52f2, U+5614, U+5875, U+5a2f, U+5b54, U+5ce0,
|
||||
U+5dba, U+5deb, U+5e63, U+5f59, U+5fcc, U+6068, U+6367, U+68b6, U+6a0b, U+6b64, U+6e15, U+6eba,
|
||||
U+7272, U+72a0, U+7947, U+7985, U+79e6, U+79e9, U+7a3d, U+7a9f, U+7aaf, U+7b95, U+7f60, U+7f9e,
|
||||
U+7fe0, U+8098, U+80ba, U+8106, U+82d4, U+831c, U+87f9, U+8a1f, U+8acf, U+90c1, U+920d, U+9756,
|
||||
U+fe43, U+ff94;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.71.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+af, U+2465, U+2517, U+33a1, U+4f10, U+50c5, U+51b4, U+5384, U+5606, U+5bb0, U+5cac, U+5ee3,
|
||||
U+618e, U+61f2, U+62c9, U+66ab, U+66f9, U+6816, U+6960, U+6b3e, U+6f20, U+7078, U+72d0, U+73ed,
|
||||
U+7ad9, U+7b1b, U+7be4, U+7d62, U+7f51, U+80b4, U+80f4, U+8154, U+85fb, U+865c, U+8702, U+895f,
|
||||
U+8aed, U+8b90, U+8ced, U+8fbf, U+91d8, U+9418, U+9583, U+9591, U+9813, U+982c, U+9bd6, U+ff46,
|
||||
U+ff7f, U+ff88;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.79.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+25b3, U+30f5, U+4eae, U+4f46, U+4f51, U+5203, U+52ff, U+55a7, U+564c, U+565b, U+57f9, U+5805,
|
||||
U+5b64, U+5e06, U+5f70, U+5f90, U+60e8, U+6182, U+62f3, U+62fe, U+63aa, U+64a4, U+65d7, U+673a,
|
||||
U+6851, U+68cb, U+68df, U+6d1e, U+6e58, U+6e9d, U+77b3, U+7832, U+7c3f, U+7db4, U+7f70, U+80aa,
|
||||
U+80c6, U+8105, U+819d, U+8276, U+8679, U+8986, U+8c9d, U+8fc5, U+916c, U+9665, U+9699, U+96c0,
|
||||
U+9a19, U+ff8b;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.82.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+2103, U+5049, U+52b1, U+5320, U+5553, U+572d, U+58c7, U+5b5d, U+5bc2, U+5de3, U+5e61, U+5f80,
|
||||
U+61a9, U+67d0, U+67f4, U+6c88, U+6ca1, U+6ce5, U+6d78, U+6e9c, U+6f54, U+731b, U+73b2, U+74a7,
|
||||
U+74f6, U+75e9, U+7b20, U+7c8b, U+7f72, U+809d, U+8108, U+82b3, U+82bd, U+84b8, U+84c4, U+88c2,
|
||||
U+8ae6, U+8ef8, U+902e, U+9065, U+9326, U+935b, U+938c, U+9676, U+9694, U+96f7, U+9ed9, U+ff48,
|
||||
U+ff4c, U+ff81;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.83.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+2500, U+3008-3009, U+4ead, U+4f0f, U+4fca, U+53eb, U+543e, U+57a2, U+5cf0, U+5e8f, U+5fe0,
|
||||
U+61b2, U+62d8, U+6442, U+64b2, U+6589, U+659c, U+67f1, U+68c4, U+6cb8, U+6d12, U+6de1, U+6fe1,
|
||||
U+70c8, U+723d, U+73e0, U+7656, U+773a, U+7948, U+7b87, U+7c92, U+7d3a, U+7e1b, U+7e4a, U+819a,
|
||||
U+8358, U+83c5, U+84bc, U+864e, U+8912, U+8c9e, U+8d05, U+92fc, U+9396, U+98fd, U+99d2, U+ff64,
|
||||
U+ff7a, U+ff83;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.84.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+3014-3015, U+4e3c, U+5036, U+5075, U+533f, U+53e9, U+5531, U+5642, U+5984, U+59e6, U+5a01,
|
||||
U+5b6b, U+5c0b, U+5f25, U+6069, U+60a0, U+614e, U+62b5, U+62d2-62d3, U+6597, U+660c, U+674f,
|
||||
U+67cf, U+6841, U+6905, U+6cf3, U+6d32, U+6d69, U+6f64, U+716e, U+7761, U+7b52, U+7be0, U+7dbf,
|
||||
U+7de9, U+7f36, U+81d3, U+8302, U+8389, U+846c, U+84ee, U+8a69, U+9038, U+9d8f, U+ff47, U+ff4b,
|
||||
U+ff76, U+ff9b;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.86.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+24, U+2022, U+2212, U+221f, U+2665, U+4ecf, U+5100, U+51cd, U+52d8, U+5378, U+53f6, U+574a,
|
||||
U+5982, U+5996, U+5c1a, U+5e1d, U+5f84, U+609f, U+61a7, U+61f8, U+6398, U+63ee, U+6676, U+6691,
|
||||
U+6eb6, U+7126, U+71e5, U+7687, U+7965, U+7d17, U+80a1, U+8107, U+8266, U+85a6, U+8987, U+8ca2,
|
||||
U+8cab, U+8e0a, U+9042, U+95c7, U+9810, U+9867, U+98fc, U+ff52-ff54, U+ff61, U+ff77, U+ff98-ff99;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.87.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+b0, U+226a, U+2462, U+4e39, U+4fc3, U+4fd7, U+50be, U+50da, U+5200, U+5211, U+54f2, U+5618,
|
||||
U+596a, U+5b22, U+5bb4, U+5d50, U+60a3, U+63fa, U+658e, U+65e8, U+6669, U+6795, U+679d, U+67a0,
|
||||
U+6b3a, U+6e09, U+757f, U+7cd6, U+7dbe, U+7ffb, U+83cc, U+83f1, U+840c, U+845b, U+8846, U+8972,
|
||||
U+8a34, U+8a50, U+8a87, U+8edf, U+8ff0, U+90a6, U+9154, U+95a3, U+9663, U+9686, U+96c7, U+ff3c,
|
||||
U+ff7c, U+ff8a;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.89.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+a5, U+4e80, U+4f34, U+4f73, U+4f75, U+511f, U+5192, U+52aa, U+53c8, U+570f, U+57cb, U+596e,
|
||||
U+5d8b, U+5f66, U+5fd9, U+62db, U+62f6, U+6328, U+633f, U+63a7, U+6469, U+6bbf, U+6c41, U+6c57,
|
||||
U+6d44, U+6dbc, U+706f, U+72c2, U+72ed, U+7551, U+75f4, U+7949, U+7e26, U+7fd4, U+8150, U+8af8,
|
||||
U+8b0e, U+8b72, U+8ca7, U+934b, U+9a0e, U+9a12, U+9b42, U+ff41, U+ff43, U+ff45, U+ff49, U+ff4f,
|
||||
U+ff62-ff63;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.91.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+60, U+2200, U+226b, U+2461, U+517c, U+526f, U+5800, U+5b97, U+5bf8, U+5c01, U+5d29, U+5e4c,
|
||||
U+5e81, U+6065, U+61d0, U+667a, U+6696, U+6843, U+6c99, U+6d99, U+6ec5, U+6f22, U+6f6e, U+6fa4,
|
||||
U+6fef, U+71c3, U+72d9, U+7384, U+78e8, U+7a1a, U+7a32, U+7a3c, U+7adc, U+7ca7, U+7d2b, U+7dad,
|
||||
U+7e4b, U+80a9, U+8170, U+81ed, U+820e, U+8a17, U+8afe, U+90aa, U+914e, U+963f, U+99c4, U+9eba,
|
||||
U+9f3b, U+ff38;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.93.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+21d2, U+25ce, U+300a-300b, U+4e89, U+4e9c, U+4ea1, U+5263, U+53cc, U+5426, U+5869, U+5947,
|
||||
U+598a, U+5999, U+5e55, U+5e72, U+5e79, U+5fae, U+5fb9, U+602a, U+6163, U+624d, U+6749, U+6c5a,
|
||||
U+6cbf, U+6d45, U+6dfb, U+6e7e, U+708e, U+725b, U+7763, U+79c0, U+7bc4, U+7c89, U+7e01, U+7e2e,
|
||||
U+8010, U+8033, U+8c6a, U+8cc3, U+8f1d, U+8f9b, U+8fb2, U+907f, U+90f7, U+9707, U+9818, U+9b3c,
|
||||
U+ff0a, U+ff4d;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.95.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+2193, U+25b2, U+4e4b, U+516d, U+51c4, U+529f, U+52c9, U+5360, U+5442, U+5857, U+5915, U+59eb,
|
||||
U+5a9b, U+5c3b, U+6012, U+61b6, U+62b1, U+6311, U+6577, U+65e2, U+65ec, U+6613, U+6790, U+6cb9,
|
||||
U+7372, U+76ae, U+7d5e, U+7fcc, U+88ab, U+88d5, U+8caf, U+8ddd, U+8ecd, U+8f38, U+8f9e, U+8feb,
|
||||
U+9063, U+90f5, U+93e1, U+968a, U+968f, U+98fe, U+9ec4, U+ff1d, U+ff27, U+ff2a, U+ff36, U+ff3b,
|
||||
U+ff3d, U+ffe5;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.97.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+7e, U+b4, U+25c6, U+2661, U+4e92, U+4eee, U+4ffa, U+5144, U+5237, U+5287, U+52b4, U+58c1,
|
||||
U+5bff, U+5c04, U+5c06, U+5e95, U+5f31, U+5f93, U+63c3, U+640d, U+6557, U+6614, U+662f, U+67d3,
|
||||
U+690d, U+6bba, U+6e6f, U+72af, U+732b, U+7518, U+7ae0, U+7ae5, U+7af6, U+822a, U+89e6, U+8a3a,
|
||||
U+8a98, U+8cb8, U+8de1, U+8e8d, U+95d8, U+961c, U+96a3, U+96ea, U+9bae, U+ff20, U+ff22, U+ff29,
|
||||
U+ff2b-ff2c;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.99.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+2191, U+505c, U+52e4, U+5305, U+535a, U+56e0, U+59bb, U+5acc, U+5b09, U+5b87, U+5c90, U+5df1,
|
||||
U+5e2d, U+5e33, U+5f3e, U+6298, U+6383, U+653b, U+6697, U+6804, U+6a39, U+6cca, U+6e90, U+6f2b,
|
||||
U+702c, U+7206, U+7236, U+7559, U+7565, U+7591, U+75c7, U+75db, U+7b4b, U+7bb1, U+7d99, U+7fbd,
|
||||
U+8131, U+885b, U+8b1d, U+8ff7, U+9003, U+9045, U+96a0, U+9732, U+990a, U+99d0, U+9e97, U+9f62,
|
||||
U+ff25, U+ff2d;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.102.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+3d, U+5e, U+25cf, U+4e0e, U+4e5d, U+4e73, U+4e94, U+4f3c, U+5009, U+5145, U+51ac, U+5238,
|
||||
U+524a, U+53f3, U+547c, U+5802, U+5922, U+5a66, U+5c0e, U+5de6, U+5fd8, U+5feb, U+6797, U+685c,
|
||||
U+6b7b, U+6c5f-6c60, U+6cc9, U+6ce2, U+6d17, U+6e21, U+7167, U+7642, U+76db, U+8001, U+821e,
|
||||
U+8857, U+89d2, U+8b1b, U+8b70, U+8cb4, U+8cde, U+8f03, U+8f2a, U+968e, U+9b54, U+9e7f, U+9ebb,
|
||||
U+ff05, U+ff33;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.103.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+500d, U+5074, U+50cd, U+5175, U+52e2, U+5352, U+5354, U+53f2, U+5409, U+56fa, U+5a18, U+5b88,
|
||||
U+5bdd, U+5ca9, U+5f92, U+5fa9, U+60a9, U+623f, U+6483, U+653f, U+666f, U+66ae, U+66f2, U+6a21,
|
||||
U+6b66, U+6bcd, U+6d5c, U+796d, U+7a4d, U+7aef, U+7b56, U+7b97, U+7c4d, U+7e04, U+7fa9, U+8377,
|
||||
U+83dc, U+83ef, U+8535, U+8863, U+88cf, U+88dc, U+8907, U+8acb, U+90ce, U+91dd, U+ff0b, U+ff0d,
|
||||
U+ff19, U+ff65;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.104.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+4e01, U+4e21, U+4e38, U+52a9, U+547d, U+592e, U+5931, U+5b63, U+5c40, U+5dde, U+5e78, U+5efa,
|
||||
U+5fa1, U+604b, U+6075, U+62c5, U+632f, U+6a19, U+6c0f, U+6c11, U+6c96, U+6e05, U+70ba, U+71b1,
|
||||
U+7387, U+7403, U+75c5, U+77ed, U+795d, U+7b54, U+7cbe, U+7d19, U+7fa4, U+8089, U+81f4, U+8208,
|
||||
U+8336, U+8457, U+8a33, U+8c4a, U+8ca0, U+8ca8, U+8cc0, U+9014, U+964d, U+9803, U+983c, U+98db,
|
||||
U+ff17, U+ff21;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.105.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+25, U+25a0, U+4e26, U+4f4e, U+5341, U+56f2, U+5bbf, U+5c45, U+5c55, U+5c5e, U+5dee, U+5e9c,
|
||||
U+5f7c, U+6255, U+627f, U+62bc, U+65cf, U+661f, U+666e, U+66dc, U+67fb, U+6975, U+6a4b, U+6b32,
|
||||
U+6df1, U+6e29, U+6fc0, U+738b, U+7686, U+7a76, U+7a81, U+7c73, U+7d75, U+7dd2, U+82e5, U+82f1,
|
||||
U+85ac, U+888b, U+899a, U+8a31, U+8a8c, U+8ab0, U+8b58, U+904a, U+9060, U+9280, U+95b2, U+984d,
|
||||
U+9ce5, U+ff18;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.106.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+30f6, U+50ac, U+5178, U+51e6, U+5224, U+52dd, U+5883, U+5897, U+590f, U+5a5a, U+5bb3, U+5c65,
|
||||
U+5e03, U+5e2b, U+5e30, U+5eb7, U+6271, U+63f4, U+64ae, U+6574, U+672b, U+679a, U+6a29-6a2a,
|
||||
U+6ca2, U+6cc1, U+6d0b, U+713c, U+74b0, U+7981, U+7a0b, U+7bc0, U+7d1a, U+7d61, U+7fd2, U+822c,
|
||||
U+8996, U+89aa, U+8cac, U+8cbb, U+8d77, U+8def, U+9020, U+9152, U+9244, U+9662, U+967a, U+96e3,
|
||||
U+9759, U+ff16;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.107.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+23, U+3c, U+2192, U+4e45, U+4efb, U+4f50, U+4f8b, U+4fc2, U+5024, U+5150, U+5272, U+5370,
|
||||
U+53bb, U+542b, U+56db, U+56e3, U+57ce, U+5bc4, U+5bcc, U+5f71, U+60aa, U+6238, U+6280, U+629c,
|
||||
U+6539, U+66ff, U+670d, U+677e-677f, U+6839, U+69cb, U+6b4c, U+6bb5, U+6e96, U+6f14, U+72ec,
|
||||
U+7389, U+7814, U+79cb, U+79d1, U+79fb, U+7a0e, U+7d0d, U+85e4, U+8d64, U+9632, U+96e2, U+9805,
|
||||
U+99ac, U+ff1e;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.109.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+266a, U+4f11, U+533a, U+5343, U+534a, U+53cd, U+5404, U+56f3, U+5b57-5b58, U+5bae, U+5c4a,
|
||||
U+5e0c, U+5e2f, U+5eab, U+5f35, U+5f79, U+614b, U+6226, U+629e, U+65c5, U+6625, U+6751, U+6821,
|
||||
U+6b69, U+6b8b, U+6bce, U+6c42, U+706b, U+7c21, U+7cfb, U+805e, U+80b2, U+82b8, U+843d, U+8853,
|
||||
U+88c5, U+8a3c, U+8a66, U+8d8a, U+8fba, U+9069, U+91cf, U+9752, U+975e, U+9999, U+ff0f-ff10,
|
||||
U+ff14-ff15;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.110.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+40, U+4e86, U+4e95, U+4f01, U+4f1d, U+4fbf, U+5099, U+5171, U+5177, U+53cb, U+53ce, U+53f0,
|
||||
U+5668, U+5712, U+5ba4, U+5ca1, U+5f85, U+60f3, U+653e, U+65ad, U+65e9, U+6620, U+6750, U+6761,
|
||||
U+6b62, U+6b74, U+6e08, U+6e80, U+7248, U+7531, U+7533, U+753a, U+77f3, U+798f, U+7f6e, U+8449,
|
||||
U+88fd, U+89b3, U+8a55, U+8ac7, U+8b77, U+8db3, U+8efd, U+8fd4, U+9031-9032, U+9580, U+9589,
|
||||
U+96d1, U+985e;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.111.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+2b, U+d7, U+300e-300f, U+4e07, U+4e8c, U+512a, U+5149, U+518d, U+5236, U+52b9, U+52d9, U+5468,
|
||||
U+578b, U+57fa, U+5b8c, U+5ba2, U+5c02, U+5de5, U+5f37, U+5f62, U+623b, U+63d0, U+652f, U+672a,
|
||||
U+6848, U+6d41, U+7136, U+7537, U+754c, U+76f4, U+79c1, U+7ba1, U+7d44, U+7d4c, U+7dcf, U+7dda,
|
||||
U+7de8, U+82b1, U+897f, U+8ca9, U+8cfc, U+904e, U+9664, U+982d, U+9858, U+98a8, U+9a13, U+ff13,
|
||||
U+ff5c;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.113.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+26, U+5f, U+2026, U+203b, U+4e09, U+4eac, U+4ed5, U+4fa1, U+5143, U+5199, U+5207, U+539f,
|
||||
U+53e3, U+53f7, U+5411, U+5473, U+5546, U+55b6, U+5929, U+597d, U+5bb9, U+5c11, U+5c4b, U+5ddd,
|
||||
U+5f97, U+5fc5, U+6295, U+6301, U+6307, U+671b, U+76f8, U+78ba, U+795e, U+7d30, U+7d39, U+7d9a,
|
||||
U+89e3, U+8a00, U+8a73, U+8a8d, U+8a9e, U+8aad, U+8abf, U+8cea, U+8eca, U+8ffd, U+904b, U+9650,
|
||||
U+ff11-ff12;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.114.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+3e, U+3005, U+4e0d, U+4e88, U+4ecb, U+4ee3, U+4ef6, U+4fdd, U+4fe1, U+500b, U+50cf, U+5186,
|
||||
U+5316, U+53d7, U+540c, U+544a, U+54e1, U+5728, U+58f2, U+5973, U+5b89, U+5c71, U+5e02, U+5e97,
|
||||
U+5f15, U+5fc3, U+5fdc, U+601d, U+611b, U+611f, U+671f, U+6728, U+6765, U+683c, U+6b21, U+6ce8,
|
||||
U+6d3b, U+6d77, U+7530, U+7740, U+7acb, U+7d50, U+826f, U+8f09, U+8fbc, U+9001, U+9053, U+91ce,
|
||||
U+9762, U+98df;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.115.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+7c, U+3080, U+4ee5, U+5148, U+516c, U+521d, U+5225, U+529b, U+52a0, U+53ef, U+56de, U+56fd,
|
||||
U+5909, U+591a, U+5b66, U+5b9f, U+5bb6, U+5bfe, U+5e73, U+5e83, U+5ea6, U+5f53, U+6027, U+610f,
|
||||
U+6210, U+6240, U+660e, U+66f4, U+66f8, U+6709, U+6771, U+697d, U+69d8, U+6a5f, U+6c34, U+6cbb,
|
||||
U+73fe, U+756a, U+7684, U+771f, U+793a, U+7f8e, U+898f, U+8a2d, U+8a71, U+8fd1, U+9078, U+9577,
|
||||
U+96fb, U+ff5e;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.116.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+a9, U+3010-3011, U+30e2, U+4e0b, U+4eca, U+4ed6, U+4ed8, U+4f53, U+4f5c, U+4f7f, U+53d6,
|
||||
U+540d, U+54c1, U+5730, U+5916, U+5b50, U+5c0f, U+5f8c, U+624b, U+6570, U+6587, U+6599, U+691c,
|
||||
U+696d, U+6cd5, U+7269, U+7279, U+7406, U+767a-767b, U+77e5, U+7d04, U+7d22, U+8005, U+80fd,
|
||||
U+81ea, U+8868, U+8981, U+89a7, U+901a, U+9023, U+90e8, U+91d1, U+9332, U+958b, U+96c6, U+9ad8,
|
||||
U+ff1a, U+ff1f;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.117.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+4e, U+a0, U+3000, U+300c-300d, U+4e00, U+4e0a, U+4e2d, U+4e8b, U+4eba, U+4f1a, U+5165, U+5168,
|
||||
U+5185, U+51fa, U+5206, U+5229, U+524d, U+52d5, U+5408, U+554f, U+5831, U+5834, U+5927, U+5b9a,
|
||||
U+5e74, U+5f0f, U+60c5, U+65b0, U+65b9, U+6642, U+6700, U+672c, U+682a, U+6b63, U+6c17, U+7121,
|
||||
U+751f, U+7528, U+753b, U+76ee, U+793e, U+884c, U+898b, U+8a18, U+9593, U+95a2, U+ff01,
|
||||
U+ff08-ff09;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.118.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+21-22, U+27-2a, U+2c-3b, U+3f, U+41-4d, U+4f-5d, U+61-7b, U+7d, U+ab, U+ae, U+b2-b3, U+b7,
|
||||
U+bb, U+c9, U+cd, U+d6, U+d8, U+dc, U+e0-e5, U+e7-ed, U+ef, U+f1-f4, U+f6, U+f8, U+fa, U+fc-fd,
|
||||
U+103, U+14d, U+1b0, U+300-301, U+1ebf, U+1ec7, U+2013-2014, U+201c-201d, U+2039-203a, U+203c,
|
||||
U+2048-2049, U+2113, U+2122, U+65e5, U+6708, U+70b9;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7tOBsePYl9tE4unb6zDxeJE5P6vlm-9aQ.119.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+20, U+2027, U+3001-3002, U+3041-307f, U+3081-308f, U+3091-3093, U+3099-309a, U+309d-309e,
|
||||
U+30a1-30e1, U+30e3-30ed, U+30ef-30f0, U+30f2-30f4, U+30fb-30fe, U+ff0c, U+ff0e;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7dNHkPD5g.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
||||
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||||
U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Tsukimi Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/tsukimirounded/v14/sJoc3LJNksWZO0LvnZwkF3HtoB7dOnkP.woff2)
|
||||
format('woff2');
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
||||
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
167
src/style/global.css
Executable file
167
src/style/global.css
Executable file
@@ -0,0 +1,167 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap');
|
||||
@import 'tailwindcss';
|
||||
@import './animations.css';
|
||||
@custom-variant light (&:where(.light, .light *));
|
||||
|
||||
* {
|
||||
transition: all 0.2s ease-in allow-discrete;
|
||||
}
|
||||
|
||||
@utility scroll-hidden {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@theme {
|
||||
/* Shadow variables (unchanged) */
|
||||
--shadow-terminal:
|
||||
rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px,
|
||||
rgba(0, 0, 02, 0.5) 0px -4px 0px inset;
|
||||
|
||||
--shadow-button:
|
||||
rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px,
|
||||
rgba(0, 0, 02, 0.4) 0px -3px 0px inset;
|
||||
|
||||
--shadow-bg: rgba(0, 0, 0, 0.25) 0px 14px 28px, rgba(0, 0, 0, 0.22) 0px 10px 10px;
|
||||
|
||||
--shadow-subtle:
|
||||
rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 3px 4px -3px,
|
||||
rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
|
||||
|
||||
--background-image-dots-bg: radial-gradient(
|
||||
circle at center,
|
||||
var(--dots-bg-2) 1px,
|
||||
var(--dots-bg-1) 1px
|
||||
);
|
||||
|
||||
/* Background colors */
|
||||
--color-bg-dark-dark: oklch(0.1 0.01 292);
|
||||
--color-bg-dark: oklch(0.15 0.03 292);
|
||||
--color-bg-mid-dark: oklch(0.175 0.03 292);
|
||||
--color-bg-light-dark: oklch(0.2 0.035 292);
|
||||
--color-bg-lighter-dark: oklch(0.25 0.04 292);
|
||||
--color-bg-dark-light: oklch(0.8 0.15 280);
|
||||
--color-bg-light: oklch(0.85 0.2 280);
|
||||
--color-bg-light-light: oklch(0.9 0.3 280);
|
||||
--color-bg-lighter-light: oklch(1 0.3 280);
|
||||
|
||||
/* Text colors */
|
||||
--color-text-dark: oklch(0.96 0.01 292);
|
||||
--color-text-muted-dark: oklch(0.76 0.01 292);
|
||||
--color-text-light: oklch(0.15 0.02 292);
|
||||
--color-text-muted-light: oklch(0.4 0.02 292);
|
||||
|
||||
/* Highlight and border colors */
|
||||
--color-highlight-dark: oklch(0.6 0.02 292);
|
||||
--color-border-dark: oklch(0.3 0.02 292);
|
||||
--color-border-muted-dark: oklch(0.2 0.02 292);
|
||||
--color-highlight-light: oklch(1 0.02 280);
|
||||
--color-border-light: oklch(0.6 0.02 280);
|
||||
--color-border-muted-light: oklch(0.7 0.02 280);
|
||||
|
||||
/* Primary and secondary colors */
|
||||
--color-primary-dark: oklch(0.55 0.15 292);
|
||||
--color-primary-hover-dark: oklch(0.65 0.18 292);
|
||||
--color-secondary-dark: oklch(0.7 0.12 120);
|
||||
--color-primary-light: oklch(0.6 0.1 292);
|
||||
--color-primary-hover-light: oklch(0.65 0.1 292);
|
||||
--color-secondary-light: oklch(0.4 0.1 112);
|
||||
|
||||
/* Loader colors (no light equivalent, using dark values) */
|
||||
--color-loader-primary-dark: oklch(0.55 0.15 292);
|
||||
--color-loader-primary-hl-dark: oklch(0.8 0.15 292);
|
||||
--color-loader-primary-light: oklch(0.55 0.15 292);
|
||||
--color-loader-primary-hl-light: oklch(0.8 0.15 292);
|
||||
|
||||
/* Status colors */
|
||||
--color-danger-dark: oklch(0.7 0.05 30);
|
||||
--color-warning-dark: oklch(0.7 0.05 100);
|
||||
--color-success-dark: oklch(0.7 0.05 160);
|
||||
--color-info-dark: oklch(0.7 0.05 260);
|
||||
--color-danger-light: oklch(0.5 0.05 30);
|
||||
--color-warning-light: oklch(0.5 0.05 100);
|
||||
--color-success-light: oklch(0.5 0.05 160);
|
||||
--color-info-light: oklch(0.5 0.05 260);
|
||||
|
||||
/* Font variables (unchanged) */
|
||||
--font-primary: 'Space Mono', monospace;
|
||||
--font-terminal: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
:root {
|
||||
transition: all 0.2s ease-in;
|
||||
--dots-bg-1: oklch(0.1 0.01 292);
|
||||
--dots-bg-2: oklch(0.2 0.01 292);
|
||||
|
||||
--border-cart: solid 1px var(--border);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px,
|
||||
rgba(0, 0, 0, 0.2) 0px -3px 0px inset;
|
||||
|
||||
--gradient-bg-direction: 0deg;
|
||||
--gradient-bg: linear-gradient(var(--gradient-bg-direction), var(--bg-dark) 80%, transparent);
|
||||
|
||||
--gradient: linear-gradient(0deg, var(--bg) 95%, var(--bg-light));
|
||||
--gradient-hover: linear-gradient(0deg, var(--bg), var(--bg-light));
|
||||
|
||||
--transition-standard: all 0.2s ease-in;
|
||||
--transition-theme: all 0.2s ease;
|
||||
--lang-html: oklch(0.6 0.08207 195.16);
|
||||
--lang-css: oklch(0.5 0.16037 303.368);
|
||||
--lang-js: oklch(0.8 0.18261 102.094);
|
||||
--lang-php: oklch(0.5923 0.09155 272.038);
|
||||
--lang-cs: oklch(0.55 0.27927 141.446);
|
||||
--lang-cpp: oklch(0.6 0.15576 6.88);
|
||||
|
||||
/*
|
||||
|
||||
#underline-bg
|
||||
|
||||
/single side/ linear-gradient(90deg, var(--text) 85%, transparent 100%);
|
||||
/both sides/ radial-gradient(circle, var(--text) 85%, transparent 100%);
|
||||
|
||||
#misc
|
||||
|
||||
background-image: radial-gradient(circle at center, #9f7aea20 1px, transparent 1px); background-size: 20px 20px;
|
||||
(dotted background)
|
||||
|
||||
*/
|
||||
|
||||
main.light {
|
||||
--dots-bg-1: oklch(0.9 0.01 292);
|
||||
--dots-bg-2: oklch(0.8 0.01 292);
|
||||
|
||||
--bg-dark: oklch(0.92 0.2 280);
|
||||
--bg: oklch(0.94 0.3 280);
|
||||
--bg-light: oklch(0.96 0.3 280);
|
||||
|
||||
--text: oklch(0.15 0.02 292);
|
||||
--text-muted: oklch(0.4 0.02 292);
|
||||
|
||||
--highlight: oklch(1 0.02 280);
|
||||
--border: oklch(0.6 0.02 280);
|
||||
--border-muted: oklch(0.7 0.02 280);
|
||||
|
||||
--border-cart: solid 1px var(--bg);
|
||||
--shadow:
|
||||
oklch(0.72 0.01 292 / 0.4) 0px 2px 4px, oklch(0.62 0.01 292 / 0.3) 0px 7px 13px -3px,
|
||||
oklch(0.52 0.01 292 / 0.2) 0px -3px 0px inset;
|
||||
|
||||
--primary: oklch(0.6 0.1 292);
|
||||
--primary-hover: oklch(0.65 0.1 292);
|
||||
--secondary: oklch(0.4 0.1 112);
|
||||
|
||||
--gradient: linear-gradient(0deg, var(--bg), var(--bg-light) 95%);
|
||||
--gradient-hover: linear-gradient(0deg, var(--bg), var(--bg-light));
|
||||
--box-shadow-muted:
|
||||
rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px;
|
||||
|
||||
--danger: oklch(0.5 0.05 30);
|
||||
--warning: oklch(0.5 0.05 100);
|
||||
--success: oklch(0.5 0.05 160);
|
||||
--info: oklch(0.5 0.05 260);
|
||||
}
|
||||
}
|
||||
3
static/robots.txt
Executable file
3
static/robots.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
14
svelte-portfolio.code-workspace
Executable file
14
svelte-portfolio.code-workspace
Executable file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../../Server/www/dev.chaosmaker"
|
||||
},
|
||||
{
|
||||
"path": "../svelte-website"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
17
svelte.config.js
Executable file
17
svelte.config.js
Executable file
@@ -0,0 +1,17 @@
|
||||
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://svelte.dev/docs/kit/integrations
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
13
tailwind.config.ts
Executable file
13
tailwind.config.ts
Executable file
@@ -0,0 +1,13 @@
|
||||
import type { Config } from "tailwindcss"
|
||||
export default {
|
||||
content: [],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
dots-bg-p: "oklch(var(--dots-bg-1))",
|
||||
dots-bg-s: "oklch(var(--dots-bg-1))",
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
} satisfies Config
|
||||
19
tsconfig.json
Executable file
19
tsconfig.json
Executable file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
36
vite.config.ts
Executable file
36
vite.config.ts
Executable file
@@ -0,0 +1,36 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
test: {
|
||||
expect: { requireAssertions: true },
|
||||
projects: [
|
||||
{
|
||||
extends: './vite.config.ts',
|
||||
test: {
|
||||
name: 'client',
|
||||
environment: 'browser',
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: 'playwright',
|
||||
instances: [{ browser: 'chromium' }]
|
||||
},
|
||||
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/lib/server/**'],
|
||||
setupFiles: ['./vitest-setup-client.ts']
|
||||
}
|
||||
},
|
||||
{
|
||||
extends: './vite.config.ts',
|
||||
test: {
|
||||
name: 'server',
|
||||
environment: 'node',
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
2
vitest-setup-client.ts
Executable file
2
vitest-setup-client.ts
Executable file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="@vitest/browser/matchers" />
|
||||
/// <reference types="@vitest/browser/providers/playwright" />
|
||||
Reference in New Issue
Block a user