├── svelte-tutorial ├── src │ ├── lib │ │ ├── Counter.svelte │ │ ├── Portal.svelte │ │ ├── Summary.svelte │ │ ├── Clocks.svelte │ │ ├── Hero.svelte │ │ ├── Calendar.svelte │ │ ├── Form.svelte │ │ └── layouts │ │ │ └── Layout.svelte │ ├── vite-env.d.ts │ ├── main.js │ ├── App.svelte │ ├── assets │ │ └── svelte.svg │ ├── utils │ │ └── index.js │ ├── index.css │ └── fanta.css ├── .vscode │ └── extensions.json ├── vite.config.js ├── svelte.config.js ├── .gitignore ├── package.json ├── index.html ├── jsconfig.json ├── public │ └── vite.svg └── README.md ├── .gitattributes ├── vue-tutorial ├── .vscode │ └── extensions.json ├── src │ ├── components │ │ ├── Summary.vue │ │ ├── Portal.vue │ │ ├── layouts │ │ │ └── Layout.vue │ │ ├── Clocks.vue │ │ ├── HelloWorld.vue │ │ ├── Hero.vue │ │ ├── Calendar.vue │ │ └── Form.vue │ ├── main.js │ ├── assets │ │ └── vue.svg │ ├── App.vue │ ├── utils │ │ └── index.js │ ├── style.css │ └── fanta.css ├── vite.config.js ├── .gitignore ├── package.json ├── index.html ├── public │ └── vite.svg └── package-lock.json ├── reactjs-tutorial ├── vite.config.js ├── src │ ├── main.jsx │ ├── components │ │ ├── Portal.jsx │ │ ├── Summary.jsx │ │ ├── layouts │ │ │ └── Layout.jsx │ │ ├── Clocks.jsx │ │ ├── Hero.jsx │ │ ├── Calendar.jsx │ │ └── Form.jsx │ ├── App.jsx │ ├── assets │ │ └── react.svg │ ├── utils │ │ └── index.js │ ├── index.css │ └── fanta.css ├── .gitignore ├── index.html ├── package.json ├── README.md ├── eslint.config.js └── public │ └── vite.svg └── README.md /svelte-tutorial/src/lib/Counter.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /vue-tutorial/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /svelte-tutorial/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /svelte-tutorial/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /vue-tutorial/src/components/Summary.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /vue-tutorial/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import './fanta.css' 4 | import App from './App.vue' 5 | 6 | createApp(App).mount('#app') 7 | -------------------------------------------------------------------------------- /vue-tutorial/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | }) 8 | -------------------------------------------------------------------------------- /reactjs-tutorial/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /svelte-tutorial/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | }) 8 | -------------------------------------------------------------------------------- /svelte-tutorial/src/main.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'svelte' 2 | import './fanta.css' 3 | import './index.css' 4 | import App from './App.svelte' 5 | 6 | const app = mount(App, { 7 | target: document.getElementById('app'), 8 | }) 9 | 10 | export default app 11 | -------------------------------------------------------------------------------- /svelte-tutorial/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import './fanta.css' 5 | import App from './App.jsx' 6 | 7 | createRoot(document.getElementById('root')).render( 8 | 9 | 10 | , 11 | ) 12 | -------------------------------------------------------------------------------- /vue-tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /svelte-tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /reactjs-tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .env 27 | -------------------------------------------------------------------------------- /svelte-tutorial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-tutorial", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 13 | "svelte": "^5.20.2", 14 | "vite": "^6.2.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vue-tutorial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-tutorial", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "vue": "^3.5.13" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^5.2.1", 16 | "vite": "^6.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /svelte-tutorial/src/lib/Portal.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
{}} 10 | onclick={handleCloseModal} 11 | class="portal-underlay" 12 | >
13 |
14 | {@render form()} 15 |
16 |
17 | -------------------------------------------------------------------------------- /vue-tutorial/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/components/Portal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDom from 'react-dom' 3 | 4 | export default function Portal(props) { 5 | const { handleCloseModal, children } = props 6 | 7 | return ReactDom.createPortal( 8 |
9 |
10 |
11 | {children} 12 |
13 |
, 14 | document.getElementById('portal') 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /vue-tutorial/src/components/Portal.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /svelte-tutorial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Unalive 9 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /svelte-tutorial/src/App.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | {#snippet headache({ 11 | data, 12 | birthDate, 13 | name, 14 | percentage, 15 | lifeExpectancy, 16 | handleToggleModal, 17 | resetData, 18 | })} 19 | 20 | 21 | 22 | 23 | {/snippet} 24 | 25 | -------------------------------------------------------------------------------- /vue-tutorial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Unalive 9 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /reactjs-tutorial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Unalive 9 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /svelte-tutorial/src/lib/Summary.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 |

12 | 13 | You'll probably die in the year {finalYear} at the 14 | age of {lifeExpectancy}. 15 |

16 |
17 |

18 | Yesterday is history, tomorrow is a mystery, and today is a gift. That's 19 | why they call it the present. 20 |

21 |

Master Oogway

22 |
23 | -------------------------------------------------------------------------------- /reactjs-tutorial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactjs-tutorial", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^19.0.0", 14 | "react-dom": "^19.0.0" 15 | }, 16 | "devDependencies": { 17 | "@eslint/js": "^9.21.0", 18 | "@types/react": "^19.0.10", 19 | "@types/react-dom": "^19.0.4", 20 | "@vitejs/plugin-react": "^4.3.4", 21 | "eslint": "^9.21.0", 22 | "eslint-plugin-react-hooks": "^5.1.0", 23 | "eslint-plugin-react-refresh": "^0.4.19", 24 | "globals": "^15.15.0", 25 | "vite": "^6.2.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/components/Summary.jsx: -------------------------------------------------------------------------------- 1 | export default function Summary(props) { 2 | const { lifeExpectancy, birthDate } = props 3 | const finalYear = parseInt(birthDate.split('-')[0]) + lifeExpectancy 4 | return ( 5 | 6 |
7 |
8 |

9 | You'll probably die in the year {finalYear} at the age of {lifeExpectancy}. 10 |

11 |
12 |

Yesterday is history, tomorrow is a mystery, and today is a gift.
That's why they call it the present.

13 |

Master Oogway

14 |
15 | ) 16 | } -------------------------------------------------------------------------------- /vue-tutorial/src/components/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /svelte-tutorial/src/lib/Clocks.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |

Time remaining in different units.

11 |
12 | {#each Object.keys(data) as clock, clockIndex} 13 |
14 |
15 |
16 |
17 |
18 |
{data[clock]}
19 |

{clock}

20 |
21 |
22 | {/each} 23 |
24 |
25 | -------------------------------------------------------------------------------- /reactjs-tutorial/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. 13 | -------------------------------------------------------------------------------- /reactjs-tutorial/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | 6 | export default [ 7 | { ignores: ['dist'] }, 8 | { 9 | files: ['**/*.{js,jsx}'], 10 | languageOptions: { 11 | ecmaVersion: 2020, 12 | globals: globals.browser, 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | ecmaFeatures: { jsx: true }, 16 | sourceType: 'module', 17 | }, 18 | }, 19 | plugins: { 20 | 'react-hooks': reactHooks, 21 | 'react-refresh': reactRefresh, 22 | }, 23 | rules: { 24 | ...js.configs.recommended.rules, 25 | ...reactHooks.configs.recommended.rules, 26 | 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], 27 | 'react-refresh/only-export-components': [ 28 | 'warn', 29 | { allowConstantExport: true }, 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /vue-tutorial/src/components/Clocks.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /vue-tutorial/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | 39 | 44 | -------------------------------------------------------------------------------- /svelte-tutorial/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "bundler", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | /** 7 | * svelte-preprocess cannot figure out whether you have 8 | * a value or a type, so tell TypeScript to enforce using 9 | * `import type` instead of `import` for Types. 10 | */ 11 | "verbatimModuleSyntax": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | * To have warnings / errors of the Svelte compiler at the 16 | * correct position, enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | /** 22 | * Typecheck JS in `.svelte` and `.js` files by default. 23 | * Disable this if you'd like to use dynamic types. 24 | */ 25 | "checkJs": true 26 | }, 27 | /** 28 | * Use global.d.ts instead of compilerOptions.types 29 | * to avoid limiting type declarations. 30 | */ 31 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] 32 | } 33 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/components/layouts/Layout.jsx: -------------------------------------------------------------------------------- 1 | export default function Layout(props) { 2 | const { children } = props 3 | 4 | const header = ( 5 |
6 |

7 | Unalive 8 |

9 |
10 | ) 11 | 12 | const footer = ( 13 | 21 | ) 22 | 23 | return ( 24 | <> 25 | {header} 26 | < main > 27 | {children} 28 | 29 | {footer} 30 | 31 | ) 32 | } -------------------------------------------------------------------------------- /reactjs-tutorial/src/components/Clocks.jsx: -------------------------------------------------------------------------------- 1 | import { getTimePercentage } from "../utils" 2 | 3 | function Clock(props) { 4 | const { percent, title, data } = props 5 | return ( 6 |
7 |
8 |
9 |
10 |
11 |
{data}
12 |

{title}

13 |
14 |
15 | ) 16 | } 17 | 18 | 19 | export default function Clocks(props) { 20 | const { data } = props 21 | const snapshot = getTimePercentage() 22 | return ( 23 |
24 |

Time remaining in different units.

25 |
26 | {Object.keys(data).map((clock, clockIndex) => { 27 | return ( 28 | 29 | ) 30 | })} 31 |
32 |
33 | ) 34 | } 35 | 36 | -------------------------------------------------------------------------------- /svelte-tutorial/src/lib/Hero.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |

8 | {name}, you have {data["weeks"]} weeks left. Make them count 🫡 9 |

10 |
11 | 12 | 19 | 20 |
21 |
22 |
23 |
24 | 25 |
Birth
26 |
27 |
{percentage}
28 |
29 |
30 |
Death
31 | 32 |
33 |
34 |
35 | 36 | 38 | -------------------------------------------------------------------------------- /svelte-tutorial/src/lib/Calendar.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |

Each square of dots represents 52 weeks / 1 year of your life.

14 | 15 |
16 | {#each yearsArr as year, yearIndex} 17 |
18 | {#each weeksArr.map((val, valIndex) => { 19 | const currWeek = yearIndex * 52 + valIndex; 20 | const dotStyle = currWeek == finalWeek - 1 ? " death" : currWeek < weekNum ? " solid" : currWeek == weekNum ? " pulse" : " "; 21 | return { week: val, currWeek, dotStyle }; 22 | }) as week, weekIndex} 23 |
24 | {/each} 25 |
26 | {/each} 27 |
28 |
29 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/components/Hero.jsx: -------------------------------------------------------------------------------- 1 | export default function Hero(props) { 2 | const { name, resetData, data, percentage, handleToggleModal } = props 3 | 4 | return ( 5 |
6 |

7 | {name}, you have {data.weeks} weeks left. Make them count 🫡 8 |

9 |
10 | 11 | 15 | 16 |
17 |
18 |
19 |
20 | 21 |
Birth
22 |
23 |
{percentage}
24 |
25 |
26 |
Death
27 | 28 |
29 |
30 |
31 | ) 32 | } -------------------------------------------------------------------------------- /svelte-tutorial/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-tutorial/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /reactjs-tutorial/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-tutorial/src/components/Hero.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/components/Calendar.jsx: -------------------------------------------------------------------------------- 1 | function DozenDisplay(props) { 2 | const { weeksArr, yearIndex, weekNum, finalWeek } = props 3 | return ( 4 |
5 | {weeksArr.map((week, weekIndex) => { 6 | const currWeek = yearIndex * 52 + weekIndex 7 | const dotStyle = currWeek == finalWeek - 1 ? 8 | ' death' : 9 | currWeek < weekNum ? 10 | ' solid' : 11 | currWeek == weekNum ? 12 | ' pulse' : '' 13 | 14 | return ( 15 |
16 | ) 17 | })} 18 |
19 | ) 20 | } 21 | 22 | export default function Calendar(props) { 23 | const { lifeExpectancy, data } = props 24 | const yearsArr = [...Array(lifeExpectancy).keys()] 25 | const weeksArr = [...Array(52).keys()] 26 | 27 | const weekNum = lifeExpectancy * 52 - parseInt(data['weeks']) 28 | const finalWeek = lifeExpectancy * 52 29 | 30 | return ( 31 |
32 |

Each square of dots represents 52 weeks / 1 year of your life.

33 |
34 | {yearsArr.map((year, yearIndex) => { 35 | return ( 36 | 37 | ) 38 | })} 39 |
40 |
41 | ) 42 | } -------------------------------------------------------------------------------- /vue-tutorial/src/components/Calendar.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /svelte-tutorial/src/assets/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svelte-tutorial/src/lib/Form.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 |
33 |
34 |

Your Details

35 | 42 |
43 |
44 | 45 | 46 |
47 |
48 |
Birthday
49 |
50 | 55 | 60 | 65 |
66 |
67 |
68 | 69 | 76 |
77 | 82 |
83 | -------------------------------------------------------------------------------- /vue-tutorial/src/components/Form.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import Calendar from "./components/Calendar" 3 | import Clocks from "./components/Clocks" 4 | import Form from "./components/Form" 5 | import Hero from "./components/Hero" 6 | import Layout from "./components/layouts/Layout" 7 | import Portal from "./components/Portal" 8 | import Summary from "./components/Summary" 9 | import { calculateTimeLeft, getLifePercentageLived } from "./utils" 10 | 11 | 12 | function App() { 13 | const [name, setName] = useState('James') 14 | const [birthDate, setBirthDate] = useState('1995-06-15') 15 | const [lifeExpectancy, setLifeExpectancy] = useState(80) 16 | const [showModal, setShowModal] = useState(false) 17 | const [data, setData] = useState(calculateTimeLeft(birthDate, lifeExpectancy)) 18 | 19 | function handleToggleModal() { 20 | setShowModal(curr => !curr) 21 | } 22 | 23 | function resetData() { 24 | setName('James') 25 | setBirthDate('1995-06-15') 26 | setLifeExpectancy(80) 27 | localStorage.clear() 28 | } 29 | 30 | function handleUpdateData(n, b, e) { 31 | if (!n || !b || !e) { return } 32 | 33 | // save data to local storage so that i can read it in later 34 | localStorage.setItem('formData', JSON.stringify({ name: n, birthDate: b, lifeExpectancy: e })) 35 | 36 | setName(n) 37 | setBirthDate(b) 38 | setLifeExpectancy(parseInt(e)) 39 | handleToggleModal() 40 | } 41 | 42 | const percentage = getLifePercentageLived(birthDate, lifeExpectancy) 43 | 44 | 45 | useEffect(() => { 46 | if (!localStorage) { return } 47 | if (localStorage.getItem('formData')) { 48 | const { name: n, birthDate: b, lifeExpectancy: e } = JSON.parse(localStorage.getItem('formData')) 49 | setName(n) 50 | setBirthDate(b) 51 | setLifeExpectancy(parseInt(e)) 52 | } 53 | }, []) 54 | 55 | useEffect(() => { 56 | const interval = setInterval(() => { 57 | setData(calculateTimeLeft(birthDate, lifeExpectancy)) 58 | }, 1000) 59 | return () => { clearInterval(interval) } 60 | }, [birthDate, lifeExpectancy]) 61 | 62 | return ( 63 | 64 | {showModal && ( 65 | 66 |
67 | 68 | )} 69 | 70 | 71 | 72 | 73 | 74 | ) 75 | } 76 | 77 | export default App 78 | -------------------------------------------------------------------------------- /vue-tutorial/src/App.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /svelte-tutorial/README.md: -------------------------------------------------------------------------------- 1 | # Svelte + Vite 2 | 3 | This template should help get you started developing with Svelte in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). 8 | 9 | ## Need an official Svelte framework? 10 | 11 | Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. 12 | 13 | ## Technical considerations 14 | 15 | **Why use this over SvelteKit?** 16 | 17 | - It brings its own routing solution which might not be preferable for some users. 18 | - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. 19 | 20 | This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. 21 | 22 | Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. 23 | 24 | **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** 25 | 26 | Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. 27 | 28 | **Why include `.vscode/extensions.json`?** 29 | 30 | Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. 31 | 32 | **Why enable `checkJs` in the JS template?** 33 | 34 | It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration. 35 | 36 | **Why is HMR not preserving my local component state?** 37 | 38 | HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/sveltejs/svelte-hmr/tree/master/packages/svelte-hmr#preservation-of-local-state). 39 | 40 | If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. 41 | 42 | ```js 43 | // store.js 44 | // An extremely simple external store 45 | import { writable } from 'svelte/store' 46 | export default writable(0) 47 | ``` 48 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/components/Form.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | export default function Form(props) { 4 | const { handleCloseModal, handleUpdateData } = props 5 | const [name, setName] = useState('James') 6 | const [lifeExpectancy, setLifeExpectancy] = useState(80) 7 | 8 | const [month, setMonth] = useState(1) // stored as index from 1-12 9 | const [day, setDay] = useState(1) 10 | const [year, setYear] = useState(new Date().getFullYear()) 11 | 12 | const days = Array.from({ length: 31 }, (_, i) => i + 1) 13 | const months = [ 14 | "January", "February", "March", "April", "May", "June", 15 | "July", "August", "September", "October", "November", "December" 16 | ] 17 | const years = Array.from({ length: 100 }, (_, i) => new Date().getFullYear() - i) 18 | 19 | return ( 20 |
21 |
22 |

Your Details

23 | 26 |
27 |
28 | 29 | { 30 | setName(e.target.value) 31 | }} type='text' required /> 32 |
33 |
34 | 35 |
36 | 46 | 56 | 66 |
67 |
68 |
69 | 70 | { 71 | setLifeExpectancy(e.target.value) 72 | }} type='number' max={120} required /> 73 |
74 | 79 |
80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /svelte-tutorial/src/lib/layouts/Layout.svelte: -------------------------------------------------------------------------------- 1 | 82 | 83 | {#if showModal} 84 | 85 | {#snippet form()} 86 | 87 | {/snippet} 88 | 89 | {/if} 90 | 91 |
92 |

Unalive

93 |
94 | 95 |
96 | 97 | {@render headache({ 98 | data, 99 | birthDate, 100 | name, 101 | percentage, 102 | lifeExpectancy, 103 | handleToggleModal, 104 | resetData, 105 | })} 106 |
107 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /reactjs-tutorial/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-tutorial/src/utils/index.js: -------------------------------------------------------------------------------- 1 | export function calculateTimeLeft(birthDate, lifeExpectancy) { 2 | const now = new Date() 3 | const birth = new Date(birthDate) 4 | const expectedDeath = new Date(birth.setFullYear(birth.getFullYear() + lifeExpectancy)) 5 | 6 | if (now >= expectedDeath) { 7 | console.log("You have surpassed the life expectancy!") 8 | return ({ 9 | years: '-', 10 | months: '-', 11 | weeks: '-', 12 | days: '-', 13 | hours: '-', 14 | minutes: '-', 15 | seconds: '-' 16 | }) 17 | } 18 | 19 | const millisecondsLeft = expectedDeath - now 20 | const secondsLeft = Math.floor(millisecondsLeft / 1000) 21 | const minutesLeft = Math.floor(secondsLeft / 60) 22 | const hoursLeft = Math.floor(minutesLeft / 60) 23 | const daysLeft = Math.floor(hoursLeft / 24) 24 | const weeksLeft = Math.floor(daysLeft / 7) 25 | 26 | // Approximate months and years 27 | const monthsLeft = Math.floor(daysLeft / 30.44) 28 | const yearsLeft = Math.floor(monthsLeft / 12) 29 | 30 | return { 31 | years: yearsLeft, 32 | months: monthsLeft, 33 | weeks: weeksLeft, 34 | days: daysLeft, 35 | hours: hoursLeft, 36 | minutes: minutesLeft, 37 | seconds: secondsLeft 38 | } 39 | } 40 | 41 | export function getTimePercentage() { 42 | const now = new Date() 43 | 44 | // Year 45 | const startOfYear = new Date(now.getFullYear(), 0, 1) 46 | const endOfYear = new Date(now.getFullYear() + 1, 0, 1) 47 | const yearPercentage = ((now - startOfYear) / (endOfYear - startOfYear)) * 100 48 | 49 | // Month 50 | const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) 51 | const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) 52 | const monthPercentage = ((now - startOfMonth) / (endOfMonth - startOfMonth)) * 100 53 | 54 | // Week (ISO week: Monday = 0, Sunday = 6) 55 | const startOfWeek = new Date(now) 56 | startOfWeek.setDate(now.getDate() - now.getDay()) 57 | startOfWeek.setHours(0, 0, 0, 0) 58 | const endOfWeek = new Date(startOfWeek) 59 | endOfWeek.setDate(startOfWeek.getDate() + 7) 60 | const weekPercentage = ((now - startOfWeek) / (endOfWeek - startOfWeek)) * 100 61 | 62 | // Day 63 | const startOfDay = new Date(now) 64 | startOfDay.setHours(0, 0, 0, 0) 65 | const endOfDay = new Date(startOfDay) 66 | endOfDay.setDate(startOfDay.getDate() + 1) 67 | const dayPercentage = ((now - startOfDay) / (endOfDay - startOfDay)) * 100 68 | 69 | // Hour 70 | const startOfHour = new Date(now) 71 | startOfHour.setMinutes(0, 0, 0) 72 | const endOfHour = new Date(startOfHour) 73 | endOfHour.setHours(startOfHour.getHours() + 1) 74 | const hourPercentage = ((now - startOfHour) / (endOfHour - startOfHour)) * 100 75 | 76 | // Minute 77 | const startOfMinute = new Date(now) 78 | startOfMinute.setSeconds(0, 0) 79 | const endOfMinute = new Date(startOfMinute) 80 | endOfMinute.setMinutes(startOfMinute.getMinutes() + 1) 81 | const minutePercentage = ((now - startOfMinute) / (endOfMinute - startOfMinute)) * 100 82 | 83 | return { 84 | years: yearPercentage.toFixed(1) * 360 / 100, 85 | months: monthPercentage.toFixed(1) * 360 / 100, 86 | weeks: weekPercentage.toFixed(1) * 360 / 100, 87 | days: dayPercentage.toFixed(1) * 360 / 100, 88 | hours: hourPercentage.toFixed(1) * 360 / 100, 89 | minutes: minutePercentage.toFixed(1) * 360 / 100 90 | } 91 | } 92 | 93 | 94 | export function getLifePercentageLived(birthDate, lifeExpectancy) { 95 | const birth = new Date(birthDate) 96 | const now = new Date() 97 | const expectedDeath = new Date(birth) 98 | expectedDeath.setFullYear(birth.getFullYear() + lifeExpectancy) 99 | 100 | const lifeLived = now - birth 101 | const totalLife = expectedDeath - birth 102 | const lifePercentage = (lifeLived / totalLife) * 100 103 | return lifePercentage.toFixed(1) + "%" 104 | } -------------------------------------------------------------------------------- /svelte-tutorial/src/utils/index.js: -------------------------------------------------------------------------------- 1 | export function calculateTimeLeft(birthDate, lifeExpectancy) { 2 | const now = new Date() 3 | const birth = new Date(birthDate) 4 | const expectedDeath = new Date(birth.setFullYear(birth.getFullYear() + lifeExpectancy)) 5 | 6 | if (now >= expectedDeath) { 7 | console.log("You have surpassed the life expectancy!") 8 | return ({ 9 | years: '-', 10 | months: '-', 11 | weeks: '-', 12 | days: '-', 13 | hours: '-', 14 | minutes: '-', 15 | seconds: '-' 16 | }) 17 | } 18 | 19 | const millisecondsLeft = expectedDeath - now 20 | const secondsLeft = Math.floor(millisecondsLeft / 1000) 21 | const minutesLeft = Math.floor(secondsLeft / 60) 22 | const hoursLeft = Math.floor(minutesLeft / 60) 23 | const daysLeft = Math.floor(hoursLeft / 24) 24 | const weeksLeft = Math.floor(daysLeft / 7) 25 | 26 | // Approximate months and years 27 | const monthsLeft = Math.floor(daysLeft / 30.44) 28 | const yearsLeft = Math.floor(monthsLeft / 12) 29 | 30 | return { 31 | years: yearsLeft, 32 | months: monthsLeft, 33 | weeks: weeksLeft, 34 | days: daysLeft, 35 | hours: hoursLeft, 36 | minutes: minutesLeft, 37 | seconds: secondsLeft 38 | } 39 | } 40 | 41 | export function getTimePercentage() { 42 | const now = new Date() 43 | 44 | // Year 45 | const startOfYear = new Date(now.getFullYear(), 0, 1) 46 | const endOfYear = new Date(now.getFullYear() + 1, 0, 1) 47 | const yearPercentage = ((now - startOfYear) / (endOfYear - startOfYear)) * 100 48 | 49 | // Month 50 | const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) 51 | const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) 52 | const monthPercentage = ((now - startOfMonth) / (endOfMonth - startOfMonth)) * 100 53 | 54 | // Week (ISO week: Monday = 0, Sunday = 6) 55 | const startOfWeek = new Date(now) 56 | startOfWeek.setDate(now.getDate() - now.getDay()) 57 | startOfWeek.setHours(0, 0, 0, 0) 58 | const endOfWeek = new Date(startOfWeek) 59 | endOfWeek.setDate(startOfWeek.getDate() + 7) 60 | const weekPercentage = ((now - startOfWeek) / (endOfWeek - startOfWeek)) * 100 61 | 62 | // Day 63 | const startOfDay = new Date(now) 64 | startOfDay.setHours(0, 0, 0, 0) 65 | const endOfDay = new Date(startOfDay) 66 | endOfDay.setDate(startOfDay.getDate() + 1) 67 | const dayPercentage = ((now - startOfDay) / (endOfDay - startOfDay)) * 100 68 | 69 | // Hour 70 | const startOfHour = new Date(now) 71 | startOfHour.setMinutes(0, 0, 0) 72 | const endOfHour = new Date(startOfHour) 73 | endOfHour.setHours(startOfHour.getHours() + 1) 74 | const hourPercentage = ((now - startOfHour) / (endOfHour - startOfHour)) * 100 75 | 76 | // Minute 77 | const startOfMinute = new Date(now) 78 | startOfMinute.setSeconds(0, 0) 79 | const endOfMinute = new Date(startOfMinute) 80 | endOfMinute.setMinutes(startOfMinute.getMinutes() + 1) 81 | const minutePercentage = ((now - startOfMinute) / (endOfMinute - startOfMinute)) * 100 82 | 83 | return { 84 | years: yearPercentage.toFixed(1) * 360 / 100, 85 | months: monthPercentage.toFixed(1) * 360 / 100, 86 | weeks: weekPercentage.toFixed(1) * 360 / 100, 87 | days: dayPercentage.toFixed(1) * 360 / 100, 88 | hours: hourPercentage.toFixed(1) * 360 / 100, 89 | minutes: minutePercentage.toFixed(1) * 360 / 100 90 | } 91 | } 92 | 93 | 94 | export function getLifePercentageLived(birthDate, lifeExpectancy) { 95 | const birth = new Date(birthDate) 96 | const now = new Date() 97 | const expectedDeath = new Date(birth) 98 | expectedDeath.setFullYear(birth.getFullYear() + lifeExpectancy) 99 | 100 | const lifeLived = now - birth 101 | const totalLife = expectedDeath - birth 102 | const lifePercentage = (lifeLived / totalLife) * 100 103 | return lifePercentage.toFixed(1) + "%" 104 | } -------------------------------------------------------------------------------- /reactjs-tutorial/src/utils/index.js: -------------------------------------------------------------------------------- 1 | export function calculateTimeLeft(birthDate, lifeExpectancy) { 2 | const now = new Date() 3 | const birth = new Date(birthDate) 4 | const expectedDeath = new Date(birth.setFullYear(birth.getFullYear() + parseInt(lifeExpectancy))) 5 | 6 | if (now >= expectedDeath) { 7 | console.log("You have surpassed the life expectancy!") 8 | return ({ 9 | years: '-', 10 | months: '-', 11 | weeks: '-', 12 | days: '-', 13 | hours: '-', 14 | minutes: '-', 15 | seconds: '-' 16 | }) 17 | } 18 | 19 | const millisecondsLeft = expectedDeath - now 20 | const secondsLeft = Math.floor(millisecondsLeft / 1000) 21 | const minutesLeft = Math.floor(secondsLeft / 60) 22 | const hoursLeft = Math.floor(minutesLeft / 60) 23 | const daysLeft = Math.floor(hoursLeft / 24) 24 | const weeksLeft = Math.floor(daysLeft / 7) 25 | 26 | // Approximate months and years 27 | const monthsLeft = Math.floor(daysLeft / 30.44) 28 | const yearsLeft = Math.floor(monthsLeft / 12) 29 | 30 | return { 31 | years: yearsLeft, 32 | months: monthsLeft, 33 | weeks: weeksLeft, 34 | days: daysLeft, 35 | hours: hoursLeft, 36 | minutes: minutesLeft, 37 | seconds: secondsLeft 38 | } 39 | } 40 | 41 | export function getTimePercentage() { 42 | const now = new Date() 43 | 44 | // Year 45 | const startOfYear = new Date(now.getFullYear(), 0, 1) 46 | const endOfYear = new Date(now.getFullYear() + 1, 0, 1) 47 | const yearPercentage = ((now - startOfYear) / (endOfYear - startOfYear)) * 100 48 | 49 | // Month 50 | const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) 51 | const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) 52 | const monthPercentage = ((now - startOfMonth) / (endOfMonth - startOfMonth)) * 100 53 | 54 | // Week (ISO week: Monday = 0, Sunday = 6) 55 | const startOfWeek = new Date(now) 56 | startOfWeek.setDate(now.getDate() - now.getDay()) 57 | startOfWeek.setHours(0, 0, 0, 0) 58 | const endOfWeek = new Date(startOfWeek) 59 | endOfWeek.setDate(startOfWeek.getDate() + 7) 60 | const weekPercentage = ((now - startOfWeek) / (endOfWeek - startOfWeek)) * 100 61 | 62 | // Day 63 | const startOfDay = new Date(now) 64 | startOfDay.setHours(0, 0, 0, 0) 65 | const endOfDay = new Date(startOfDay) 66 | endOfDay.setDate(startOfDay.getDate() + 1) 67 | const dayPercentage = ((now - startOfDay) / (endOfDay - startOfDay)) * 100 68 | 69 | // Hour 70 | const startOfHour = new Date(now) 71 | startOfHour.setMinutes(0, 0, 0) 72 | const endOfHour = new Date(startOfHour) 73 | endOfHour.setHours(startOfHour.getHours() + 1) 74 | const hourPercentage = ((now - startOfHour) / (endOfHour - startOfHour)) * 100 75 | 76 | // Minute 77 | const startOfMinute = new Date(now) 78 | startOfMinute.setSeconds(0, 0) 79 | const endOfMinute = new Date(startOfMinute) 80 | endOfMinute.setMinutes(startOfMinute.getMinutes() + 1) 81 | const minutePercentage = ((now - startOfMinute) / (endOfMinute - startOfMinute)) * 100 82 | 83 | return { 84 | years: yearPercentage.toFixed(1) * 360 / 100, 85 | months: monthPercentage.toFixed(1) * 360 / 100, 86 | weeks: weekPercentage.toFixed(1) * 360 / 100, 87 | days: dayPercentage.toFixed(1) * 360 / 100, 88 | hours: hourPercentage.toFixed(1) * 360 / 100, 89 | minutes: minutePercentage.toFixed(1) * 360 / 100 90 | } 91 | } 92 | 93 | 94 | export function getLifePercentageLived(birthDate, lifeExpectancy) { 95 | const birth = new Date(birthDate) 96 | const now = new Date() 97 | const expectedDeath = new Date(birth) 98 | 99 | expectedDeath.setFullYear(birth.getFullYear() + parseInt(lifeExpectancy)) 100 | 101 | const lifeLived = now - birth 102 | const totalLife = expectedDeath - birth 103 | const lifePercentage = (lifeLived / totalLife) * 100 104 | return lifePercentage.toFixed(1) + "%" 105 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frontend Full Course 2025 | Learn React.js, Svelte & Vue.js in 8 Hours 🚀 2 | 3 | Master modern frontend development in 2025 by learning **React.js**, **Svelte**, and **Vue.js** — the most popular JavaScript frameworks today! Build the modern web application **three times**, starting from scratch in each framework, and gain job-ready frontend skills that will set you apart as a developer. 4 | 5 | This is the ultimate beginner-friendly course to confidently learn the core concepts of component-based frontend frameworks, compare them side by side, and pick your favorite! 6 | 7 | [➡️ **Watch the full course for free on YouTube (7.5 hours of hands-on tutorials)!**](https://www.youtu.be/rvwwHx2pJuw) 8 | 9 | --- 10 | 11 | ### **The Course Includes** 12 | 🎥 7.5 hours of step-by-step frontend development tutorials. 13 | ⭐️ Beginner-friendly explanations of React, Svelte, and Vue fundamentals. 14 | 🧩 One real-world application built in each framework — from scratch. 15 | 🎨 Pre-styled apps so you can focus 100% on JavaScript frameworks, not CSS. 16 | 💡 Access on mobile, tablet, desktop, and smart TV. 17 | 18 | --- 19 | 20 | ### **Who This Course Is For** 21 | ✅ Beginners ready to level up their frontend skills. 22 | ✅ Developers curious about the differences between React, Svelte, and Vue. 23 | ✅ Anyone who wants to confidently choose the best frontend framework for their projects. 24 | ✅ Self-taught coders following the [smoljames.com/roadmap](https://www.smoljames.com/roadmap). 25 | 26 | --- 27 | 28 | ### **Requirements** 29 | You’ll just need some basic understanding of: 30 | - **HTML** 31 | - **CSS** 32 | - **JavaScript** 33 | 34 | If you need to learn these first, head over to my free resources at: [smoljames.com/roadmap](https://www.smoljames.com/roadmap) 🌟 35 | 36 | Any computer (Windows, macOS, or Linux) is perfect — we’ll set everything up together during the course. 37 | 38 | --- 39 | 40 | ### **Course Description** 41 | 42 | #### **Why is this the right frontend development course for you?** 43 | This course is designed to teach you modern frontend development by building a full application three times over — once with **React.js**, once with **Svelte**, and once with **Vue.js**. 44 | 45 | By the end of the course, you’ll deeply understand: 46 | - How these frameworks think. 47 | - How to build dynamic web apps. 48 | - How to manage state, handle events, and navigate routes. 49 | - And how to compare frameworks confidently! 50 | 51 | These three are not just popular — they're **foundational**. Once you’ve learned React, Svelte, and Vue, you'll have the skills to easily pick up any other frontend framework or library. 52 | 53 | Plus, our app is pre-styled to keep the focus purely on **JavaScript frameworks and development concepts.** 54 | 55 | --- 56 | 57 | ### **What You’ll Build** 58 | - **React.js:** Build a fully functional app from scratch, understanding components, hooks, and state management. 59 | - **Svelte:** Build the same app with Svelte’s reactive, compiler-first approach for lightning-fast development. 60 | - **Vue.js:** Recreate the app in Vue with its intuitive syntax and flexible features. 61 | 62 | Repetition across frameworks locks in your understanding and helps you see the differences clearly. 63 | 64 | --- 65 | 66 | ### **What You’ll Learn** 67 | - Modern frontend development workflows. 68 | - Building apps with reusable components. 69 | - State management across frameworks. 70 | - Handling events and user interactions. 71 | - Client-side routing for single-page applications (SPAs). 72 | - Understanding reactivity and lifecycle hooks. 73 | - Key similarities and differences between frameworks. 74 | - How to choose the right framework for your projects. 75 | 76 | --- 77 | 78 | ### **About Your Instructor** 79 | 80 | Hi, I’m James! 👋 81 | I’ve taught over 500,000 people how to code and start their development careers. As a self-taught programmer, I know how overwhelming it can be to choose the right tools. This is why I built this course: to give you clarity, confidence, and practical skills that employers and clients are looking for. 82 | 83 | With over 10 years of teaching experience, I specialize in making complex topics easy to understand — and most importantly, fun. 84 | 85 | Let’s get started! 86 | 87 | Learn more at [smoljames.com](https://www.smoljames.com). 88 | Connect with me anytime on Discord or LinkedIn for support! 89 | 90 | --- 91 | 92 | ### **Tags** 93 | frontend course, react.js tutorial, svelte tutorial, vue.js tutorial, javascript frameworks, frontend development 2025, learn react svelte vue, youtube coding course, smoljames, frontend frameworks for beginners, frontend roadmap, web development full course 94 | 95 | --- 96 | 97 | 🚀 **Start your frontend journey today and master React, Svelte, and Vue — all in one course!** 98 | -------------------------------------------------------------------------------- /vue-tutorial/src/style.css: -------------------------------------------------------------------------------- 1 | #app { 2 | max-width: 800px; 3 | width: 100%; 4 | margin: 0 auto; 5 | display: flex; 6 | flex-direction: column; 7 | gap: 2rem; 8 | min-height: 100vh; 9 | } 10 | 11 | header, 12 | main, 13 | footer { 14 | padding: 1rem; 15 | } 16 | 17 | main { 18 | flex: 1; 19 | gap: 4rem; 20 | } 21 | 22 | section, 23 | main { 24 | display: flex; 25 | flex-direction: column; 26 | } 27 | 28 | section { 29 | gap: 1.5rem; 30 | } 31 | 32 | .btns-container { 33 | display: flex; 34 | align-items: center; 35 | gap: 0.5rem; 36 | padding: 0.5rem 0; 37 | } 38 | 39 | .btns-container button { 40 | flex: 1; 41 | white-space: nowrap; 42 | } 43 | 44 | .progress-bar { 45 | border-radius: 4rem; 46 | border: 2px solid var(--color-link); 47 | } 48 | 49 | .progress-bar>div { 50 | padding: 0.8rem 1.4rem; 51 | } 52 | 53 | .progress-bar>div, 54 | .progress-bar { 55 | display: flex; 56 | align-items: center; 57 | justify-content: space-between; 58 | gap: 1rem; 59 | overflow: hidden; 60 | } 61 | 62 | .progress-bar>div:first-of-type { 63 | min-width: 20%; 64 | background: var(--color-link); 65 | color: var(--background-primary); 66 | } 67 | 68 | .progress-bar div div { 69 | display: flex; 70 | align-items: center; 71 | gap: 1rem; 72 | } 73 | 74 | .progress-bar .bar-label { 75 | display: none; 76 | } 77 | 78 | .clocks-grid { 79 | display: grid; 80 | grid-template-columns: repeat(2, minmax(0, 1fr)); 81 | gap: 1rem; 82 | } 83 | 84 | 85 | .clock-card { 86 | display: flex; 87 | flex-direction: column; 88 | align-items: center; 89 | justify-content: center; 90 | gap: 0.5rem; 91 | } 92 | 93 | .clock-card>div:last-of-type { 94 | display: flex; 95 | flex-direction: column; 96 | align-items: center; 97 | } 98 | 99 | .clock-card>div:last-of-type p { 100 | text-transform: lowercase; 101 | opacity: 0.7; 102 | } 103 | 104 | .circle { 105 | height: 4rem; 106 | aspect-ratio: 1/1; 107 | margin: 0.5rem 0; 108 | border-radius: 100%; 109 | border: 3px solid var(--border-primary); 110 | position: relative; 111 | } 112 | 113 | .circle .ticker { 114 | height: 50%; 115 | width: 3px; 116 | background: var(--border-secondary); 117 | border-radius: 1rem; 118 | position: absolute; 119 | top: 0; 120 | left: 50%; 121 | transform: translateX(-50%); 122 | transform-origin: bottom; 123 | } 124 | 125 | .years, 126 | .weeks, 127 | .days, 128 | .hours, 129 | .minutes, 130 | .seconds { 131 | animation: spin linear infinite; 132 | } 133 | 134 | 135 | .years { 136 | animation-duration: 31556952s; 137 | /* 1 year */ 138 | } 139 | 140 | .months { 141 | animation-duration: 2629746s; 142 | /* 1 month */ 143 | } 144 | 145 | .weeks { 146 | animation-duration: 604800s; 147 | /* 1 week */ 148 | } 149 | 150 | .days { 151 | animation-duration: 86400s; 152 | /* 1 day */ 153 | } 154 | 155 | .hours { 156 | animation-duration: 3600s; 157 | /* 1 hour */ 158 | } 159 | 160 | .minutes { 161 | animation-duration: 60s; 162 | /* 1 minute */ 163 | } 164 | 165 | .seconds { 166 | animation-duration: 1s; 167 | /* 1 minute */ 168 | } 169 | 170 | @keyframes spin { 171 | from { 172 | transform: rotate(0deg); 173 | } 174 | 175 | to { 176 | transform: rotate(360deg); 177 | } 178 | } 179 | 180 | .dozen-grid { 181 | display: grid; 182 | grid-template-columns: repeat(12, minmax(0, 1fr)); 183 | gap: 0.5rem; 184 | } 185 | 186 | .year-grid { 187 | display: grid; 188 | grid-template-columns: repeat(4, minmax(0, 1fr)); 189 | gap: 0.15rem 0; 190 | } 191 | 192 | .dot { 193 | height: 0.2rem; 194 | aspect-ratio: 1/1; 195 | border-radius: 0.2rem; 196 | border: 0.5px solid var(--color-link); 197 | } 198 | 199 | .solid { 200 | background: var(--color-link); 201 | } 202 | 203 | .pulse { 204 | background: var(--border-secondary) 205 | } 206 | 207 | .pulse { 208 | animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; 209 | } 210 | 211 | .death { 212 | /* background: lavender !important; */ 213 | border-color: violet !important; 214 | } 215 | 216 | @keyframes ping { 217 | 218 | 70%, 219 | 100% { 220 | transform: scale(2); 221 | opacity: 0; 222 | } 223 | } 224 | 225 | 226 | #summary div { 227 | padding: 1rem 2rem; 228 | border-radius: 2rem; 229 | background: var(--background-muted); 230 | text-align: center; 231 | /* border: 1px solid var(--border-secondary); */ 232 | /* color: var(--background-primary); */ 233 | margin-bottom: 1rem; 234 | } 235 | 236 | #summary { 237 | align-items: center; 238 | margin: 0 auto; 239 | } 240 | 241 | #summary h4 { 242 | text-align: center; 243 | } 244 | 245 | #summary h4:last-of-type { 246 | opacity: 0.5; 247 | } 248 | 249 | #summary p i { 250 | padding-right: 0.5rem; 251 | } 252 | 253 | footer { 254 | display: flex; 255 | flex-direction: column; 256 | gap: 1rem; 257 | align-items: center; 258 | padding: 3rem 0; 259 | padding-bottom: 5rem; 260 | } 261 | 262 | footer a { 263 | display: flex; 264 | align-items: center; 265 | gap: 0.5rem; 266 | padding: 0.3rem; 267 | padding-right: 0.5rem; 268 | background: var(--background-muted); 269 | border-radius: 4rem; 270 | border: 1px solid transparent; 271 | transition-duration: 200ms; 272 | text-decoration: none; 273 | } 274 | 275 | footer a:hover { 276 | border: 1px solid var(--color-link); 277 | } 278 | 279 | footer a img { 280 | max-width: 30px; 281 | aspect-ratio: 1/1; 282 | border-radius: 100%; 283 | } 284 | 285 | .portal-container { 286 | position: fixed; 287 | left: 0; 288 | top: 0; 289 | width: 100vw; 290 | height: 100vh; 291 | z-index: 1001; 292 | } 293 | 294 | .portal-underlay { 295 | position: absolute; 296 | inset: 0; 297 | z-index: 0; 298 | opacity: 0.7; 299 | background: var(--background-primary); 300 | border: none; 301 | padding: none; 302 | } 303 | 304 | .portal-content { 305 | position: absolute; 306 | /* pointer-events: none; */ 307 | z-index: 1005; 308 | top: 50%; 309 | left: 50%; 310 | transform: translate(-50%, -50%); 311 | width: 600px; 312 | max-width: 90vw; 313 | max-height: 80vh; 314 | display: grid; 315 | place-items: center; 316 | background: var(--background-primary); 317 | } 318 | 319 | #form { 320 | width: 100%; 321 | padding: 2rem; 322 | display: flex; 323 | flex-direction: column; 324 | gap: 1rem; 325 | } 326 | 327 | #form>div:first-of-type { 328 | display: flex; 329 | align-items: center; 330 | justify-content: space-between; 331 | gap: 1rem; 332 | } 333 | 334 | #form .bday { 335 | display: grid; 336 | grid-template-columns: repeat(3, minmax(0, 1fr)); 337 | gap: 1rem; 338 | } 339 | 340 | @media (min-width: 640px) { 341 | .btns-container { 342 | width: fit-content; 343 | } 344 | 345 | .progress-bar .bar-label { 346 | display: inline; 347 | } 348 | 349 | .clocks-grid { 350 | grid-template-columns: repeat(3, minmax(0, 1fr)); 351 | } 352 | 353 | .dozen-grid { 354 | gap: 1rem; 355 | } 356 | 357 | .year-grid { 358 | gap: 0.2rem 0; 359 | } 360 | 361 | .dot { 362 | height: 0.3rem; 363 | } 364 | 365 | } 366 | 367 | @media (min-width: 768px) { 368 | .clocks-grid { 369 | grid-template-columns: repeat(4, minmax(0, 1fr)); 370 | } 371 | 372 | .dozen-grid { 373 | gap: 1.25rem; 374 | } 375 | 376 | .year-grid { 377 | gap: 0.3rem 0; 378 | } 379 | 380 | .dot { 381 | height: 0.4rem; 382 | } 383 | } -------------------------------------------------------------------------------- /reactjs-tutorial/src/index.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 800px; 3 | width: 100%; 4 | margin: 0 auto; 5 | display: flex; 6 | flex-direction: column; 7 | gap: 2rem; 8 | min-height: 100vh; 9 | } 10 | 11 | header, 12 | main, 13 | footer { 14 | padding: 1rem; 15 | } 16 | 17 | main { 18 | flex: 1; 19 | gap: 4rem; 20 | } 21 | 22 | section, 23 | main { 24 | display: flex; 25 | flex-direction: column; 26 | } 27 | 28 | section { 29 | gap: 1.5rem; 30 | } 31 | 32 | .btns-container { 33 | display: flex; 34 | align-items: center; 35 | gap: 0.5rem; 36 | padding: 0.5rem 0; 37 | } 38 | 39 | .btns-container button { 40 | flex: 1; 41 | white-space: nowrap; 42 | } 43 | 44 | .progress-bar { 45 | border-radius: 4rem; 46 | border: 2px solid var(--color-link); 47 | } 48 | 49 | .progress-bar>div { 50 | padding: 0.8rem 1.4rem; 51 | } 52 | 53 | .progress-bar>div, 54 | .progress-bar { 55 | display: flex; 56 | align-items: center; 57 | justify-content: space-between; 58 | gap: 1rem; 59 | overflow: hidden; 60 | min-width: fit-content; 61 | } 62 | 63 | .progress-bar>div:first-of-type { 64 | min-width: fit-content; 65 | background: var(--color-link); 66 | color: var(--background-primary); 67 | } 68 | 69 | .progress-bar div div { 70 | display: flex; 71 | align-items: center; 72 | gap: 1rem; 73 | } 74 | 75 | .progress-bar .bar-label { 76 | display: none; 77 | } 78 | 79 | .clocks-grid { 80 | display: grid; 81 | grid-template-columns: repeat(2, minmax(0, 1fr)); 82 | gap: 1rem; 83 | } 84 | 85 | 86 | .clock-card { 87 | display: flex; 88 | flex-direction: column; 89 | align-items: center; 90 | justify-content: center; 91 | gap: 0.5rem; 92 | } 93 | 94 | .clock-card>div:last-of-type { 95 | display: flex; 96 | flex-direction: column; 97 | align-items: center; 98 | } 99 | 100 | .clock-card>div:last-of-type p { 101 | text-transform: lowercase; 102 | opacity: 0.7; 103 | } 104 | 105 | .circle { 106 | height: 4rem; 107 | aspect-ratio: 1/1; 108 | margin: 0.5rem 0; 109 | border-radius: 100%; 110 | border: 3px solid var(--border-primary); 111 | position: relative; 112 | } 113 | 114 | .circle .ticker { 115 | height: 50%; 116 | width: 3px; 117 | background: var(--border-secondary); 118 | border-radius: 1rem; 119 | position: absolute; 120 | top: 0; 121 | left: 50%; 122 | transform: translateX(-50%); 123 | transform-origin: bottom; 124 | } 125 | 126 | .years, 127 | .weeks, 128 | .days, 129 | .hours, 130 | .minutes, 131 | .seconds { 132 | animation: spin linear infinite; 133 | } 134 | 135 | 136 | .years { 137 | animation-duration: 31556952s; 138 | /* 1 year */ 139 | } 140 | 141 | .months { 142 | animation-duration: 2629746s; 143 | /* 1 month */ 144 | } 145 | 146 | .weeks { 147 | animation-duration: 604800s; 148 | /* 1 week */ 149 | } 150 | 151 | .days { 152 | animation-duration: 86400s; 153 | /* 1 day */ 154 | } 155 | 156 | .hours { 157 | animation-duration: 3600s; 158 | /* 1 hour */ 159 | } 160 | 161 | .minutes { 162 | animation-duration: 60s; 163 | /* 1 minute */ 164 | } 165 | 166 | .seconds { 167 | animation-duration: 1s; 168 | /* 1 minute */ 169 | } 170 | 171 | @keyframes spin { 172 | from { 173 | transform: rotate(0deg); 174 | } 175 | 176 | to { 177 | transform: rotate(360deg); 178 | } 179 | } 180 | 181 | .dozen-grid { 182 | display: grid; 183 | grid-template-columns: repeat(12, minmax(0, 1fr)); 184 | gap: 0.5rem; 185 | } 186 | 187 | .year-grid { 188 | display: grid; 189 | grid-template-columns: repeat(4, minmax(0, 1fr)); 190 | gap: 0.15rem 0; 191 | } 192 | 193 | .dot { 194 | height: 0.2rem; 195 | aspect-ratio: 1/1; 196 | border-radius: 0.2rem; 197 | border: 0.5px solid var(--color-link); 198 | } 199 | 200 | .solid { 201 | background: var(--color-link); 202 | } 203 | 204 | .pulse { 205 | background: var(--border-secondary) 206 | } 207 | 208 | .pulse { 209 | animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; 210 | } 211 | 212 | .death { 213 | /* background: lavender !important; */ 214 | border-color: violet !important; 215 | } 216 | 217 | @keyframes ping { 218 | 219 | 70%, 220 | 100% { 221 | transform: scale(2); 222 | opacity: 0; 223 | } 224 | } 225 | 226 | 227 | #summary div { 228 | padding: 1rem 2rem; 229 | border-radius: 2rem; 230 | background: var(--background-muted); 231 | text-align: center; 232 | /* border: 1px solid var(--border-secondary); */ 233 | /* color: var(--background-primary); */ 234 | margin-bottom: 1rem; 235 | } 236 | 237 | #summary { 238 | align-items: center; 239 | margin: 0 auto; 240 | } 241 | 242 | #summary h4 { 243 | text-align: center; 244 | } 245 | 246 | #summary h4:last-of-type { 247 | opacity: 0.5; 248 | } 249 | 250 | #summary p i { 251 | padding-right: 0.5rem; 252 | } 253 | 254 | footer { 255 | display: flex; 256 | flex-direction: column; 257 | gap: 1rem; 258 | align-items: center; 259 | padding: 3rem 0; 260 | padding-bottom: 5rem; 261 | } 262 | 263 | footer a { 264 | display: flex; 265 | align-items: center; 266 | gap: 0.5rem; 267 | padding: 0.3rem; 268 | padding-right: 0.5rem; 269 | background: var(--background-muted); 270 | border-radius: 4rem; 271 | border: 1px solid transparent; 272 | transition-duration: 200ms; 273 | text-decoration: none; 274 | } 275 | 276 | footer a:hover { 277 | border: 1px solid var(--color-link); 278 | } 279 | 280 | footer a img { 281 | max-width: 30px; 282 | aspect-ratio: 1/1; 283 | border-radius: 100%; 284 | } 285 | 286 | .portal-container { 287 | position: fixed; 288 | left: 0; 289 | top: 0; 290 | width: 100vw; 291 | height: 100vh; 292 | z-index: 1001; 293 | } 294 | 295 | .portal-underlay { 296 | position: absolute; 297 | inset: 0; 298 | z-index: 0; 299 | opacity: 0.7; 300 | background: var(--background-primary); 301 | border: none; 302 | padding: none; 303 | } 304 | 305 | .portal-content { 306 | position: absolute; 307 | /* pointer-events: none; */ 308 | z-index: 1005; 309 | top: 50%; 310 | left: 50%; 311 | transform: translate(-50%, -50%); 312 | width: 600px; 313 | max-width: 90vw; 314 | max-height: 80vh; 315 | display: grid; 316 | place-items: center; 317 | background: var(--background-primary); 318 | } 319 | 320 | #form { 321 | width: 100%; 322 | padding: 2rem; 323 | display: flex; 324 | flex-direction: column; 325 | gap: 1rem; 326 | } 327 | 328 | #form>div:first-of-type { 329 | display: flex; 330 | align-items: center; 331 | justify-content: space-between; 332 | gap: 1rem; 333 | } 334 | 335 | #form .bday { 336 | display: grid; 337 | grid-template-columns: repeat(3, minmax(0, 1fr)); 338 | gap: 1rem; 339 | } 340 | 341 | @media (min-width: 640px) { 342 | .btns-container { 343 | width: fit-content; 344 | } 345 | 346 | .progress-bar .bar-label { 347 | display: inline; 348 | } 349 | 350 | .clocks-grid { 351 | grid-template-columns: repeat(3, minmax(0, 1fr)); 352 | } 353 | 354 | .dozen-grid { 355 | gap: 1rem; 356 | } 357 | 358 | .year-grid { 359 | gap: 0.2rem 0; 360 | } 361 | 362 | .dot { 363 | height: 0.3rem; 364 | } 365 | 366 | } 367 | 368 | @media (min-width: 768px) { 369 | .clocks-grid { 370 | grid-template-columns: repeat(4, minmax(0, 1fr)); 371 | } 372 | 373 | .dozen-grid { 374 | gap: 1.25rem; 375 | } 376 | 377 | .year-grid { 378 | gap: 0.3rem 0; 379 | } 380 | 381 | .dot { 382 | height: 0.4rem; 383 | } 384 | } -------------------------------------------------------------------------------- /svelte-tutorial/src/index.css: -------------------------------------------------------------------------------- 1 | #app { 2 | max-width: 800px; 3 | width: 100%; 4 | margin: 0 auto; 5 | display: flex; 6 | flex-direction: column; 7 | gap: 2rem; 8 | min-height: 100vh; 9 | } 10 | 11 | header, 12 | main, 13 | footer { 14 | padding: 1rem; 15 | } 16 | 17 | main { 18 | flex: 1; 19 | gap: 4rem; 20 | } 21 | 22 | section, 23 | main { 24 | display: flex; 25 | flex-direction: column; 26 | } 27 | 28 | section { 29 | gap: 1.5rem; 30 | } 31 | 32 | .btns-container { 33 | display: flex; 34 | align-items: center; 35 | gap: 0.5rem; 36 | padding: 0.5rem 0; 37 | } 38 | 39 | .btns-container button { 40 | flex: 1; 41 | white-space: nowrap; 42 | } 43 | 44 | .progress-bar { 45 | border-radius: 4rem; 46 | border: 2px solid var(--color-link); 47 | } 48 | 49 | .progress-bar>div { 50 | padding: 0.8rem 1.4rem; 51 | } 52 | 53 | .progress-bar>div, 54 | .progress-bar { 55 | display: flex; 56 | align-items: center; 57 | justify-content: space-between; 58 | gap: 1rem; 59 | overflow: hidden; 60 | min-width: fit-content; 61 | } 62 | 63 | .progress-bar>div:first-of-type { 64 | min-width: fit-content; 65 | background: var(--color-link); 66 | color: var(--background-primary); 67 | } 68 | 69 | .progress-bar div div { 70 | display: flex; 71 | align-items: center; 72 | gap: 1rem; 73 | } 74 | 75 | .progress-bar .bar-label { 76 | display: none; 77 | } 78 | 79 | .clocks-grid { 80 | display: grid; 81 | grid-template-columns: repeat(2, minmax(0, 1fr)); 82 | gap: 1rem; 83 | } 84 | 85 | 86 | .clock-card { 87 | display: flex; 88 | flex-direction: column; 89 | align-items: center; 90 | justify-content: center; 91 | gap: 0.5rem; 92 | } 93 | 94 | .clock-card>div:last-of-type { 95 | display: flex; 96 | flex-direction: column; 97 | align-items: center; 98 | } 99 | 100 | .clock-card>div:last-of-type p { 101 | text-transform: lowercase; 102 | opacity: 0.7; 103 | } 104 | 105 | .circle { 106 | height: 4rem; 107 | aspect-ratio: 1/1; 108 | margin: 0.5rem 0; 109 | border-radius: 100%; 110 | border: 3px solid var(--border-primary); 111 | position: relative; 112 | } 113 | 114 | .circle .ticker { 115 | height: 50%; 116 | width: 3px; 117 | background: var(--border-secondary); 118 | border-radius: 1rem; 119 | position: absolute; 120 | top: 0; 121 | left: 50%; 122 | transform: translateX(-50%); 123 | transform-origin: bottom; 124 | } 125 | 126 | .years, 127 | .weeks, 128 | .days, 129 | .hours, 130 | .minutes, 131 | .seconds { 132 | animation: spin linear infinite; 133 | } 134 | 135 | 136 | .years { 137 | animation-duration: 31556952s; 138 | /* 1 year */ 139 | } 140 | 141 | .months { 142 | animation-duration: 2629746s; 143 | /* 1 month */ 144 | } 145 | 146 | .weeks { 147 | animation-duration: 604800s; 148 | /* 1 week */ 149 | } 150 | 151 | .days { 152 | animation-duration: 86400s; 153 | /* 1 day */ 154 | } 155 | 156 | .hours { 157 | animation-duration: 3600s; 158 | /* 1 hour */ 159 | } 160 | 161 | .minutes { 162 | animation-duration: 60s; 163 | /* 1 minute */ 164 | } 165 | 166 | .seconds { 167 | animation-duration: 1s; 168 | /* 1 minute */ 169 | } 170 | 171 | @keyframes spin { 172 | from { 173 | transform: rotate(0deg); 174 | } 175 | 176 | to { 177 | transform: rotate(360deg); 178 | } 179 | } 180 | 181 | .dozen-grid { 182 | display: grid; 183 | grid-template-columns: repeat(12, minmax(0, 1fr)); 184 | gap: 0.5rem; 185 | } 186 | 187 | .year-grid { 188 | display: grid; 189 | grid-template-columns: repeat(4, minmax(0, 1fr)); 190 | gap: 0.15rem 0; 191 | } 192 | 193 | .dot { 194 | height: 0.2rem; 195 | aspect-ratio: 1/1; 196 | border-radius: 0.2rem; 197 | border: 0.5px solid var(--color-link); 198 | } 199 | 200 | .solid { 201 | background: var(--color-link); 202 | } 203 | 204 | .pulse { 205 | background: var(--border-secondary) 206 | } 207 | 208 | .pulse { 209 | animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; 210 | } 211 | 212 | .death { 213 | /* background: lavender !important; */ 214 | border-color: violet !important; 215 | } 216 | 217 | @keyframes ping { 218 | 219 | 70%, 220 | 100% { 221 | transform: scale(2); 222 | opacity: 0; 223 | } 224 | } 225 | 226 | 227 | #summary div { 228 | padding: 1rem 2rem; 229 | border-radius: 2rem; 230 | background: var(--background-muted); 231 | text-align: center; 232 | /* border: 1px solid var(--border-secondary); */ 233 | /* color: var(--background-primary); */ 234 | margin-bottom: 1rem; 235 | } 236 | 237 | #summary { 238 | align-items: center; 239 | margin: 0 auto; 240 | } 241 | 242 | #summary h4 { 243 | text-align: center; 244 | } 245 | 246 | #summary h4:last-of-type { 247 | opacity: 0.5; 248 | } 249 | 250 | #summary p i { 251 | padding-right: 0.5rem; 252 | } 253 | 254 | footer { 255 | display: flex; 256 | flex-direction: column; 257 | gap: 1rem; 258 | align-items: center; 259 | padding: 3rem 0; 260 | padding-bottom: 5rem; 261 | } 262 | 263 | footer a { 264 | display: flex; 265 | align-items: center; 266 | gap: 0.5rem; 267 | padding: 0.3rem; 268 | padding-right: 0.5rem; 269 | background: var(--background-muted); 270 | border-radius: 4rem; 271 | border: 1px solid transparent; 272 | transition-duration: 200ms; 273 | text-decoration: none; 274 | } 275 | 276 | footer a:hover { 277 | border: 1px solid var(--color-link); 278 | } 279 | 280 | footer a img { 281 | max-width: 30px; 282 | aspect-ratio: 1/1; 283 | border-radius: 100%; 284 | } 285 | 286 | .portal-container { 287 | position: fixed; 288 | left: 0; 289 | top: 0; 290 | width: 100vw; 291 | height: 100vh; 292 | z-index: 1001; 293 | } 294 | 295 | .portal-underlay { 296 | position: absolute; 297 | inset: 0; 298 | z-index: 0; 299 | opacity: 0.7; 300 | background: var(--background-primary); 301 | border: none; 302 | padding: none; 303 | } 304 | 305 | .portal-content { 306 | position: absolute; 307 | /* pointer-events: none; */ 308 | z-index: 1005; 309 | top: 50%; 310 | left: 50%; 311 | transform: translate(-50%, -50%); 312 | width: 600px; 313 | max-width: 90vw; 314 | max-height: 80vh; 315 | display: grid; 316 | place-items: center; 317 | background: var(--background-primary); 318 | } 319 | 320 | #form { 321 | width: 100%; 322 | padding: 2rem; 323 | display: flex; 324 | flex-direction: column; 325 | gap: 1rem; 326 | } 327 | 328 | #form>div:first-of-type { 329 | display: flex; 330 | align-items: center; 331 | justify-content: space-between; 332 | gap: 1rem; 333 | } 334 | 335 | #form .bday { 336 | display: grid; 337 | grid-template-columns: repeat(3, minmax(0, 1fr)); 338 | gap: 1rem; 339 | } 340 | 341 | @media (min-width: 640px) { 342 | .btns-container { 343 | width: fit-content; 344 | } 345 | 346 | .progress-bar .bar-label { 347 | display: inline; 348 | } 349 | 350 | .clocks-grid { 351 | grid-template-columns: repeat(3, minmax(0, 1fr)); 352 | } 353 | 354 | .dozen-grid { 355 | gap: 1rem; 356 | } 357 | 358 | .year-grid { 359 | gap: 0.2rem 0; 360 | } 361 | 362 | .dot { 363 | height: 0.3rem; 364 | } 365 | 366 | } 367 | 368 | @media (min-width: 768px) { 369 | .clocks-grid { 370 | grid-template-columns: repeat(4, minmax(0, 1fr)); 371 | } 372 | 373 | .dozen-grid { 374 | gap: 1.25rem; 375 | } 376 | 377 | .year-grid { 378 | gap: 0.3rem 0; 379 | } 380 | 381 | .dot { 382 | height: 0.4rem; 383 | } 384 | } -------------------------------------------------------------------------------- /vue-tutorial/src/fanta.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Fanta.css (Fantasy + CSS) 3 | * Version 0.1.0 4 | * https://github.com/jamezmca/fantacss 5 | * 6 | * Sections 7 | * 1. Content sectioning 8 | * 2. Text content 9 | * 3. Inline text semantics 10 | * 4. Image and multimedia 11 | * 5. Tables 12 | * 6. Forms 13 | * 7. Interactive elements 14 | * 15 | */ 16 | 17 | /* @import url('https://fonts.googleapis.com/css2?family=Eczar:wght@400..800&family=Grenze:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); */ 18 | @import url('https://fonts.googleapis.com/css2?family=Eczar:wght@400..800&family=Press+Start+2P&display=swap'); 19 | 20 | /* :root { 21 | --background-primary: white; 22 | --background-secondary: ''; 23 | --background-tertiary: #dbeafe; 24 | --background-accent: ''; 25 | --background-compliment: ''; 26 | --background-gradient: ''; 27 | --background-muted: #f8fafc; 28 | 29 | --color-primary: #030615; 30 | --color-secondary: ''; 31 | --color-tertiary: ''; 32 | --color-accent: ''; 33 | --color-compliment: ''; 34 | --color-gradient: ''; 35 | --color-muted: ; 36 | --color-link: #2563eb; 37 | --color-link-transparent: rgba(37, 99, 235, 0.1); 38 | 39 | --color-success: ''; 40 | --color-warning: ''; 41 | --color-error: ''; 42 | --color-info: ''; 43 | --color-highlight: #fef9c3; 44 | 45 | --gradient-start: #9580ff; 46 | --gradient-end: #80ffea; 47 | 48 | --border-primary: #f1f5f9; 49 | --border-secondary: #bed1e7; 50 | --border-highlight: #64748b; 51 | --border-tertiary: ''; 52 | 53 | --shadow-dark: ''; 54 | --shadow-light: ''; 55 | --shadow-text: ''; 56 | 57 | --padding-small: 1rem; 58 | --padding-large: 2rem; 59 | 60 | --border-radius-small: 0.5rem; 61 | --border-radius-large: 0.75rem; 62 | --highlight-border-radius: 0.5rem; 63 | 64 | --text-selection: ''; 65 | } */ 66 | 67 | /* @media (prefers-color-scheme: dark) { */ 68 | :root { 69 | --background-primary: #030615; 70 | --background-secondary: rgb(23 37 84); 71 | --background-tertiary: #121424; 72 | --background-accent: ''; 73 | --background-compliment: ''; 74 | --background-gradient: ''; 75 | --background-muted: #1a1e32; 76 | 77 | --color-primary: rgb(219 234 254); 78 | --color-secondary: ''; 79 | --color-tertiary: ''; 80 | --color-accent: ''; 81 | --color-compliment: ''; 82 | --color-gradient: ''; 83 | --color-muted: ; 84 | --color-link: #60a5fa; 85 | --color-link-transparent: rgba(37, 99, 235, 0.1); 86 | 87 | --color-success: ''; 88 | --color-warning: ''; 89 | --color-error: ''; 90 | --color-info: ''; 91 | --color-highlight: #fef9c3; 92 | 93 | --gradient-start: #0084ff; 94 | --gradient-end: #00fffb; 95 | 96 | --border-primary: #29325b; 97 | --border-secondary: #93c5fd; 98 | --border-highlight: #4649af; 99 | --border-tertiary: ''; 100 | 101 | --shadow-dark: ''; 102 | --shadow-light: ''; 103 | --shadow-text: ''; 104 | 105 | --padding-small: 1rem; 106 | --padding-large: 2rem; 107 | 108 | --border-radius-small: 0.5rem; 109 | --border-radius-large: 0.75rem; 110 | --highlight-border-radius: 0.5rem; 111 | 112 | --text-selection: ''; 113 | } 114 | 115 | /* } */ 116 | 117 | * { 118 | margin: 0; 119 | padding: 0; 120 | box-sizing: border-box; 121 | font-family: "Eczar", serif; 122 | } 123 | 124 | body { 125 | background: var(--background-primary); 126 | color: var(--color-primary); 127 | font-size: 0.875rem; 128 | line-height: 1.6rem; 129 | } 130 | 131 | /* Special */ 132 | .text-gradient { 133 | -webkit-text-fill-color: transparent; 134 | -webkit-background-clip: text; 135 | background-image: linear-gradient(180deg, var(--gradient-start) 0, var(--gradient-end) 100%); 136 | background-size: 100%; 137 | -webkit-box-decoration-break: clone; 138 | } 139 | 140 | 141 | /* Typography */ 142 | h1, 143 | h2, 144 | h3, 145 | h4, 146 | h5, 147 | h6, 148 | button { 149 | font-family: "Press Start 2P", serif; 150 | width: fit-content; 151 | } 152 | 153 | /* h1, */ 154 | .text-large { 155 | font-size: 1.875rem; 156 | line-height: 2.25rem; 157 | } 158 | 159 | h1 { 160 | font-size: 1.5rem; 161 | line-height: 2rem; 162 | } 163 | 164 | .special-shadow { 165 | text-shadow: -1px -1px 4px #0022ffbf, 2px 2px 10px #0022ffbf, 0 0 20px #0022ffbf; 166 | color: white; 167 | } 168 | 169 | h2 { 170 | font-size: 1.25rem; 171 | line-height: 1.75rem; 172 | } 173 | 174 | h3, 175 | .text-medium { 176 | font-size: 1.125rem; 177 | line-height: 1.75rem; 178 | } 179 | 180 | h4 { 181 | font-size: 1rem; 182 | line-height: 1.5rem; 183 | } 184 | 185 | h5, 186 | h6 { 187 | font-size: 0.875rem; 188 | line-height: 1.25rem; 189 | } 190 | 191 | p { 192 | display: block; 193 | width: fit-content; 194 | } 195 | 196 | span { 197 | font-weight: inherit; 198 | font-size: inherit; 199 | line-height: inherit; 200 | } 201 | 202 | /* Content Sectioning */ 203 | 204 | main {} 205 | 206 | header {} 207 | 208 | footer {} 209 | 210 | nav {} 211 | 212 | section {} 213 | 214 | address { 215 | font-style: normal; 216 | } 217 | 218 | aside { 219 | float: right; 220 | width: 40%; 221 | padding: 0.75rem; 222 | margin: 0.5rem; 223 | font-style: italic; 224 | color: var(--color-primary); 225 | background-color: var(--background-muted); 226 | border-radius: var(--border-radius-large); 227 | } 228 | 229 | 230 | /* Text Content */ 231 | blockquote { 232 | position: relative; 233 | padding-left: 1.5rem; 234 | margin: 0; 235 | } 236 | 237 | blockquote::after { 238 | content: ""; 239 | display: block; 240 | position: absolute; 241 | left: 0; 242 | top: 0; 243 | height: 100%; 244 | border-left: 7px solid var(--border-primary); 245 | border-radius: 6px; 246 | } 247 | 248 | blockquote footer { 249 | padding-top: 1rem; 250 | } 251 | 252 | dd { 253 | padding-bottom: 11px; 254 | } 255 | 256 | dl {} 257 | 258 | dt { 259 | font-weight: bold; 260 | } 261 | 262 | figure {} 263 | 264 | figcaption { 265 | padding-top: 10px; 266 | font-size: 0.8rem; 267 | } 268 | 269 | hr {} 270 | 271 | 272 | ul, 273 | ol { 274 | list-style-position: inside; 275 | padding-left: 1rem; 276 | } 277 | 278 | li { 279 | line-height: 1.6em; 280 | } 281 | 282 | /* Inline Text Elements */ 283 | 284 | a { 285 | color: var(--color-link); 286 | } 287 | 288 | a:active, 289 | a:focus, 290 | a:hover { 291 | text-decoration: none; 292 | } 293 | 294 | .link-button { 295 | border: none; 296 | color: var(--color-primary); 297 | background: var(--background-primary); 298 | box-shadow: none; 299 | padding: 0.5rem 1rem; 300 | } 301 | 302 | .link-button:hover { 303 | box-shadow: none; 304 | transform: unset; 305 | text-decoration: underline; 306 | } 307 | 308 | mark, 309 | samp, 310 | kbd, 311 | code, 312 | time { 313 | border-radius: var(--highlight-border-radius, 4px); 314 | box-decoration-break: clone; 315 | -webkit-box-decoration-break: clone; 316 | } 317 | 318 | mark { 319 | background-color: var(--color-highlight); 320 | padding: 0 4px; 321 | } 322 | 323 | samp { 324 | font-weight: bold; 325 | padding: 0.5rem 1rem; 326 | background-color: var(--background-muted); 327 | color: var(--color-primary); 328 | } 329 | 330 | kbd, 331 | time { 332 | padding: 0rem 0.5rem; 333 | background-color: var(--background-muted); 334 | color: var(--color-primary); 335 | } 336 | 337 | code, 338 | pre { 339 | font-size: 0.9em; 340 | padding: 0.2rem 0.5rem; 341 | background: var(--background-muted); 342 | border: 1px solid var(--border-primary); 343 | max-width: fit-content; 344 | overflow-x: auto; 345 | } 346 | 347 | pre>code { 348 | padding: 10px; 349 | border: 0; 350 | display: block; 351 | overflow-x: auto; 352 | } 353 | 354 | pre { 355 | border-radius: var(--border-radius-large); 356 | } 357 | 358 | sup, 359 | sub { 360 | line-height: normal; 361 | } 362 | 363 | /* Image and multimedia */ 364 | audio { 365 | width: 100%; 366 | } 367 | 368 | audio, 369 | img, 370 | video { 371 | border-radius: var(--border-radius-large); 372 | max-width: 100%; 373 | } 374 | 375 | img { 376 | height: auto; 377 | } 378 | 379 | /* Tables */ 380 | table { 381 | width: fit-content; 382 | border: 1px solid var(--border-primary); 383 | background: var(--background-muted); 384 | border-radius: var(--border-radius-small); 385 | } 386 | 387 | table tr:last-child td:first-child { 388 | border-bottom-left-radius: 8px; 389 | } 390 | 391 | table tr:last-child td:last-child { 392 | border-bottom-right-radius: 8px; 393 | } 394 | 395 | table tr:first-child th:first-child { 396 | border-top-left-radius: 8px; 397 | } 398 | 399 | table tr:first-child th:last-child { 400 | border-top-right-radius: 8px; 401 | } 402 | 403 | th { 404 | background-color: var(--background-muted); 405 | } 406 | 407 | td { 408 | background: var(--background-primary); 409 | } 410 | 411 | td, 412 | th { 413 | text-align: left; 414 | padding: 8px; 415 | } 416 | 417 | thead { 418 | border-collapse: collapse; 419 | } 420 | 421 | tfoot { 422 | border-top: 1px solid black; 423 | } 424 | 425 | table tr:hover td, 426 | tbody tr:nth-child(even):hover td { 427 | background-color: var(--background-muted); 428 | } 429 | 430 | /* Form elements */ 431 | input, 432 | button, 433 | select, 434 | optgroup, 435 | textarea { 436 | /* margin: 0; */ 437 | } 438 | 439 | button, 440 | select, 441 | input[type="submit"], 442 | input[type="button"], 443 | input[type="checkbox"], 444 | input[type="range"], 445 | input[type="radio"] { 446 | cursor: pointer; 447 | } 448 | 449 | button { 450 | color: var(--color-primary); 451 | background-color: var(--background-secondary); 452 | width: fit-content; 453 | font-family: inherit; 454 | font-size: inherit; 455 | font-weight: 500; 456 | padding: 0.25rem 1.25rem; 457 | border: 1.5px solid var(--border-secondary); 458 | border-radius: var(--border-radius-small); 459 | box-shadow: 2px 2px 0 0 var(--border-secondary); 460 | transition-duration: 200ms; 461 | } 462 | 463 | button:hover { 464 | box-shadow: 0 0 0 0 var(--border-secondary); 465 | transform: translate(2px, 2px); 466 | } 467 | 468 | button[disabled]:hover { 469 | box-shadow: 2px 2px 0 0 var(--border-secondary); 470 | transform: translate(0, 0); 471 | } 472 | 473 | button:disabled, 474 | button[disabled] { 475 | border: 1.5px solid var(--border-secondary); 476 | cursor: initial; 477 | opacity: 0.55; 478 | } 479 | 480 | label { 481 | display: block; 482 | max-width: fit-content; 483 | font-weight: 500; 484 | } 485 | 486 | input, 487 | textarea, 488 | select { 489 | font-size: 1em; 490 | background-color: var(--background-muted); 491 | border: 1px solid var(--border-secondary); 492 | color: var(--color-primary); 493 | padding: 0.5rem 0.75rem; 494 | border-radius: var(--border-radius-small); 495 | width: 100%; 496 | /* max-width: fit-content; */ 497 | outline: none; 498 | appearance: none; 499 | } 500 | 501 | input:disabled { 502 | cursor: not-allowed; 503 | opacity: 0.6; 504 | } 505 | 506 | input[type="number"].buttonless { 507 | appearance: textfield; 508 | -moz-appearance: textfield; 509 | } 510 | 511 | input[type="number"].buttonless::-webkit-inner-spin-button { 512 | display: none; 513 | appearance: none; 514 | } 515 | 516 | input[type="number"].buttonless::-webkit-outer-spin-button { 517 | -webkit-appearance: none; 518 | margin: 0; 519 | } 520 | 521 | input[type="checkbox"], 522 | input[type="radio"] { 523 | padding: 0.5rem; 524 | width: fit-content; 525 | vertical-align: middle; 526 | position: relative; 527 | margin: 0.5rem 0.25rem 0.5rem 0.5rem; 528 | /* margin-right: 0.33em; 529 | margin-top: 0.31em; */ 530 | } 531 | 532 | input[type="checkbox"] { 533 | border-radius: 7px; 534 | margin-left: 0; 535 | } 536 | 537 | input[type="radio"] { 538 | border-radius: 100%; 539 | } 540 | 541 | input[type="checkbox"]:checked, 542 | input[type="radio"]:checked { 543 | background: var(--border-secondary); 544 | } 545 | 546 | input[type="range"] { 547 | vertical-align: middle; 548 | padding: 0; 549 | } 550 | 551 | input[type="color"] { 552 | appearance: none; 553 | border: none; 554 | outline-style: none; 555 | padding: initial; 556 | max-width: initial; 557 | height: 2rem; 558 | width: 3rem; 559 | } 560 | 561 | select:is([multiple]) { 562 | background: none; 563 | height: fit-content; 564 | } 565 | 566 | fieldset:focus-within, 567 | input:focus-within, 568 | textarea:focus-within, 569 | select:focus-within { 570 | border-color: var(--border-highlight); 571 | } 572 | 573 | fieldset:hover, 574 | input:hover, 575 | textarea:hover, 576 | select:hover { 577 | border-color: var(--border-highlight); 578 | } 579 | 580 | progress { 581 | appearance: none; 582 | height: 1rem; 583 | margin: 0.75rem 0; 584 | } 585 | 586 | progress::-webkit-progress-bar { 587 | background: var(--background-main); 588 | border: 1px solid var(--border-primary); 589 | border-radius: var(--highlight-border-radius); 590 | } 591 | 592 | progress::-webkit-progress-value { 593 | background-color: var(--color-link); 594 | border-radius: var(--border-radius-small); 595 | } 596 | 597 | progress::-moz-progress-bar { 598 | background-color: var(--color-link); 599 | border-radius: var(--border-radius-small); 600 | } 601 | 602 | fieldset { 603 | border: 1px solid var(--border-primary); 604 | border-radius: var(--border-radius-small); 605 | margin: 0; 606 | margin-bottom: 6px; 607 | padding: 1rem; 608 | max-width: fit-content; 609 | } 610 | 611 | /* Interactive elements */ 612 | details { 613 | border: 1px solid var(--border-primary); 614 | border-radius: var(--border-radius-small); 615 | padding: 0.5rem 0.75rem; 616 | } 617 | 618 | summary { 619 | font-weight: bold; 620 | } 621 | 622 | details[open] summary { 623 | border-bottom: 1px solid var(--border-primary); 624 | margin-bottom: 0.5rem; 625 | } 626 | 627 | .card, 628 | .button-card { 629 | background-color: var(--background-muted); 630 | color: var(--color-primary); 631 | padding: 1rem; 632 | border-radius: 0.5rem; 633 | } 634 | 635 | .card { 636 | border: 1px solid var(--color-link-transparent); 637 | } 638 | 639 | .button-card { 640 | border: 1px solid var(--border-secondary); 641 | } 642 | 643 | .button-card { 644 | box-shadow: none; 645 | } 646 | 647 | .button-card:hover { 648 | transform: translate(0); 649 | box-shadow: none; 650 | border-color: var(--border-highlight); 651 | } 652 | 653 | .card-button-primary, 654 | .card-button-secondary { 655 | border: none !important; 656 | box-shadow: none !important; 657 | } 658 | 659 | .card-button-primary { 660 | color: var(--background-primary); 661 | background: var(--color-link); 662 | } 663 | 664 | .card-button-secondary { 665 | color: var(--color-link); 666 | background: var(--color-link-transparent); 667 | } 668 | 669 | .card-button-primary:hover, 670 | .card-button-secondary:hover { 671 | transform: none; 672 | opacity: 0.6; 673 | } 674 | 675 | 676 | @media (min-width: 640px) { 677 | body { 678 | font-size: 1rem; 679 | line-height: 1.5rem; 680 | } 681 | 682 | /* h1, */ 683 | .text-large { 684 | font-size: 2.25rem; 685 | line-height: 2.5rem; 686 | } 687 | 688 | h1 { 689 | font-size: 1.875rem; 690 | line-height: 2.25rem; 691 | } 692 | 693 | h2 { 694 | font-size: 1.5rem; 695 | line-height: 2rem; 696 | } 697 | 698 | h3, 699 | .text-medium { 700 | font-size: 1.25rem; 701 | line-height: 1.75rem; 702 | } 703 | 704 | h4 { 705 | font-size: 1.125rem; 706 | line-height: 1.75rem; 707 | } 708 | 709 | h5, 710 | h6 { 711 | font-size: 1rem; 712 | line-height: 1.5rem; 713 | } 714 | 715 | button { 716 | width: fit-content; 717 | } 718 | 719 | input { 720 | max-width: 600px; 721 | } 722 | } -------------------------------------------------------------------------------- /reactjs-tutorial/src/fanta.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Fanta.css (Fantasy + CSS) 3 | * Version 0.1.0 4 | * https://github.com/jamezmca/fantacss 5 | * 6 | * Sections 7 | * 1. Content sectioning 8 | * 2. Text content 9 | * 3. Inline text semantics 10 | * 4. Image and multimedia 11 | * 5. Tables 12 | * 6. Forms 13 | * 7. Interactive elements 14 | * 15 | */ 16 | 17 | /* @import url('https://fonts.googleapis.com/css2?family=Eczar:wght@400..800&family=Grenze:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); */ 18 | @import url('https://fonts.googleapis.com/css2?family=Eczar:wght@400..800&family=Press+Start+2P&display=swap'); 19 | 20 | /* :root { 21 | --background-primary: white; 22 | --background-secondary: ''; 23 | --background-tertiary: #dbeafe; 24 | --background-accent: ''; 25 | --background-compliment: ''; 26 | --background-gradient: ''; 27 | --background-muted: #f8fafc; 28 | 29 | --color-primary: #030615; 30 | --color-secondary: ''; 31 | --color-tertiary: ''; 32 | --color-accent: ''; 33 | --color-compliment: ''; 34 | --color-gradient: ''; 35 | --color-muted: ; 36 | --color-link: #2563eb; 37 | --color-link-transparent: rgba(37, 99, 235, 0.1); 38 | 39 | --color-success: ''; 40 | --color-warning: ''; 41 | --color-error: ''; 42 | --color-info: ''; 43 | --color-highlight: #fef9c3; 44 | 45 | --gradient-start: #9580ff; 46 | --gradient-end: #80ffea; 47 | 48 | --border-primary: #f1f5f9; 49 | --border-secondary: #bed1e7; 50 | --border-highlight: #64748b; 51 | --border-tertiary: ''; 52 | 53 | --shadow-dark: ''; 54 | --shadow-light: ''; 55 | --shadow-text: ''; 56 | 57 | --padding-small: 1rem; 58 | --padding-large: 2rem; 59 | 60 | --border-radius-small: 0.5rem; 61 | --border-radius-large: 0.75rem; 62 | --highlight-border-radius: 0.5rem; 63 | 64 | --text-selection: ''; 65 | } */ 66 | 67 | /* @media (prefers-color-scheme: dark) { */ 68 | :root { 69 | --background-primary: #030615; 70 | --background-secondary: rgb(23 37 84); 71 | --background-tertiary: #121424; 72 | --background-accent: ''; 73 | --background-compliment: ''; 74 | --background-gradient: ''; 75 | --background-muted: #1a1e32; 76 | 77 | --color-primary: rgb(219 234 254); 78 | --color-secondary: ''; 79 | --color-tertiary: ''; 80 | --color-accent: ''; 81 | --color-compliment: ''; 82 | --color-gradient: ''; 83 | --color-muted: ; 84 | --color-link: #60a5fa; 85 | --color-link-transparent: rgba(37, 99, 235, 0.1); 86 | 87 | --color-success: ''; 88 | --color-warning: ''; 89 | --color-error: ''; 90 | --color-info: ''; 91 | --color-highlight: #fef9c3; 92 | 93 | --gradient-start: #0084ff; 94 | --gradient-end: #00fffb; 95 | 96 | --border-primary: #29325b; 97 | --border-secondary: #93c5fd; 98 | --border-highlight: #4649af; 99 | --border-tertiary: ''; 100 | 101 | --shadow-dark: ''; 102 | --shadow-light: ''; 103 | --shadow-text: ''; 104 | 105 | --padding-small: 1rem; 106 | --padding-large: 2rem; 107 | 108 | --border-radius-small: 0.5rem; 109 | --border-radius-large: 0.75rem; 110 | --highlight-border-radius: 0.5rem; 111 | 112 | --text-selection: ''; 113 | } 114 | 115 | /* } */ 116 | 117 | * { 118 | margin: 0; 119 | padding: 0; 120 | box-sizing: border-box; 121 | font-family: "Eczar", serif; 122 | } 123 | 124 | body { 125 | background: var(--background-primary); 126 | color: var(--color-primary); 127 | font-size: 0.875rem; 128 | line-height: 1.6rem; 129 | } 130 | 131 | /* Special */ 132 | .text-gradient { 133 | -webkit-text-fill-color: transparent; 134 | -webkit-background-clip: text; 135 | background-image: linear-gradient(180deg, var(--gradient-start) 0, var(--gradient-end) 100%); 136 | background-size: 100%; 137 | -webkit-box-decoration-break: clone; 138 | } 139 | 140 | 141 | /* Typography */ 142 | h1, 143 | h2, 144 | h3, 145 | h4, 146 | h5, 147 | h6, 148 | button { 149 | font-family: "Press Start 2P", serif; 150 | width: fit-content; 151 | } 152 | 153 | /* h1, */ 154 | .text-large { 155 | font-size: 1.875rem; 156 | line-height: 2.25rem; 157 | } 158 | 159 | h1 { 160 | font-size: 1.5rem; 161 | line-height: 2rem; 162 | } 163 | 164 | .special-shadow { 165 | text-shadow: -1px -1px 4px #0022ffbf, 2px 2px 10px #0022ffbf, 0 0 20px #0022ffbf; 166 | color: white; 167 | } 168 | 169 | h2 { 170 | font-size: 1.25rem; 171 | line-height: 1.75rem; 172 | } 173 | 174 | h3, 175 | .text-medium { 176 | font-size: 1.125rem; 177 | line-height: 1.75rem; 178 | } 179 | 180 | h4 { 181 | font-size: 1rem; 182 | line-height: 1.5rem; 183 | } 184 | 185 | h5, 186 | h6 { 187 | font-size: 0.875rem; 188 | line-height: 1.25rem; 189 | } 190 | 191 | p { 192 | display: block; 193 | width: fit-content; 194 | } 195 | 196 | span { 197 | font-weight: inherit; 198 | font-size: inherit; 199 | line-height: inherit; 200 | } 201 | 202 | /* Content Sectioning */ 203 | 204 | main {} 205 | 206 | header {} 207 | 208 | footer {} 209 | 210 | nav {} 211 | 212 | section {} 213 | 214 | address { 215 | font-style: normal; 216 | } 217 | 218 | aside { 219 | float: right; 220 | width: 40%; 221 | padding: 0.75rem; 222 | margin: 0.5rem; 223 | font-style: italic; 224 | color: var(--color-primary); 225 | background-color: var(--background-muted); 226 | border-radius: var(--border-radius-large); 227 | } 228 | 229 | 230 | /* Text Content */ 231 | blockquote { 232 | position: relative; 233 | padding-left: 1.5rem; 234 | margin: 0; 235 | } 236 | 237 | blockquote::after { 238 | content: ""; 239 | display: block; 240 | position: absolute; 241 | left: 0; 242 | top: 0; 243 | height: 100%; 244 | border-left: 7px solid var(--border-primary); 245 | border-radius: 6px; 246 | } 247 | 248 | blockquote footer { 249 | padding-top: 1rem; 250 | } 251 | 252 | dd { 253 | padding-bottom: 11px; 254 | } 255 | 256 | dl {} 257 | 258 | dt { 259 | font-weight: bold; 260 | } 261 | 262 | figure {} 263 | 264 | figcaption { 265 | padding-top: 10px; 266 | font-size: 0.8rem; 267 | } 268 | 269 | hr {} 270 | 271 | 272 | ul, 273 | ol { 274 | list-style-position: inside; 275 | padding-left: 1rem; 276 | } 277 | 278 | li { 279 | line-height: 1.6em; 280 | } 281 | 282 | /* Inline Text Elements */ 283 | 284 | a { 285 | color: var(--color-link); 286 | } 287 | 288 | a:active, 289 | a:focus, 290 | a:hover { 291 | text-decoration: none; 292 | } 293 | 294 | .link-button { 295 | border: none; 296 | color: var(--color-primary); 297 | background: var(--background-primary); 298 | box-shadow: none; 299 | padding: 0.5rem 1rem; 300 | } 301 | 302 | .link-button:hover { 303 | box-shadow: none; 304 | transform: unset; 305 | text-decoration: underline; 306 | } 307 | 308 | mark, 309 | samp, 310 | kbd, 311 | code, 312 | time { 313 | border-radius: var(--highlight-border-radius, 4px); 314 | box-decoration-break: clone; 315 | -webkit-box-decoration-break: clone; 316 | } 317 | 318 | mark { 319 | background-color: var(--color-highlight); 320 | padding: 0 4px; 321 | } 322 | 323 | samp { 324 | font-weight: bold; 325 | padding: 0.5rem 1rem; 326 | background-color: var(--background-muted); 327 | color: var(--color-primary); 328 | } 329 | 330 | kbd, 331 | time { 332 | padding: 0rem 0.5rem; 333 | background-color: var(--background-muted); 334 | color: var(--color-primary); 335 | } 336 | 337 | code, 338 | pre { 339 | font-size: 0.9em; 340 | padding: 0.2rem 0.5rem; 341 | background: var(--background-muted); 342 | border: 1px solid var(--border-primary); 343 | max-width: fit-content; 344 | overflow-x: auto; 345 | } 346 | 347 | pre>code { 348 | padding: 10px; 349 | border: 0; 350 | display: block; 351 | overflow-x: auto; 352 | } 353 | 354 | pre { 355 | border-radius: var(--border-radius-large); 356 | } 357 | 358 | sup, 359 | sub { 360 | line-height: normal; 361 | } 362 | 363 | /* Image and multimedia */ 364 | audio { 365 | width: 100%; 366 | } 367 | 368 | audio, 369 | img, 370 | video { 371 | border-radius: var(--border-radius-large); 372 | max-width: 100%; 373 | } 374 | 375 | img { 376 | height: auto; 377 | } 378 | 379 | /* Tables */ 380 | table { 381 | width: fit-content; 382 | border: 1px solid var(--border-primary); 383 | background: var(--background-muted); 384 | border-radius: var(--border-radius-small); 385 | } 386 | 387 | table tr:last-child td:first-child { 388 | border-bottom-left-radius: 8px; 389 | } 390 | 391 | table tr:last-child td:last-child { 392 | border-bottom-right-radius: 8px; 393 | } 394 | 395 | table tr:first-child th:first-child { 396 | border-top-left-radius: 8px; 397 | } 398 | 399 | table tr:first-child th:last-child { 400 | border-top-right-radius: 8px; 401 | } 402 | 403 | th { 404 | background-color: var(--background-muted); 405 | } 406 | 407 | td { 408 | background: var(--background-primary); 409 | } 410 | 411 | td, 412 | th { 413 | text-align: left; 414 | padding: 8px; 415 | } 416 | 417 | thead { 418 | border-collapse: collapse; 419 | } 420 | 421 | tfoot { 422 | border-top: 1px solid black; 423 | } 424 | 425 | table tr:hover td, 426 | tbody tr:nth-child(even):hover td { 427 | background-color: var(--background-muted); 428 | } 429 | 430 | /* Form elements */ 431 | input, 432 | button, 433 | select, 434 | optgroup, 435 | textarea { 436 | /* margin: 0; */ 437 | } 438 | 439 | button, 440 | select, 441 | input[type="submit"], 442 | input[type="button"], 443 | input[type="checkbox"], 444 | input[type="range"], 445 | input[type="radio"] { 446 | cursor: pointer; 447 | } 448 | 449 | button { 450 | color: var(--color-primary); 451 | background-color: var(--background-secondary); 452 | width: fit-content; 453 | font-family: inherit; 454 | font-size: inherit; 455 | font-weight: 500; 456 | padding: 0.25rem 1.25rem; 457 | border: 1.5px solid var(--border-secondary); 458 | border-radius: var(--border-radius-small); 459 | box-shadow: 2px 2px 0 0 var(--border-secondary); 460 | transition-duration: 200ms; 461 | } 462 | 463 | button:hover { 464 | box-shadow: 0 0 0 0 var(--border-secondary); 465 | transform: translate(2px, 2px); 466 | } 467 | 468 | button[disabled]:hover { 469 | box-shadow: 2px 2px 0 0 var(--border-secondary); 470 | transform: translate(0, 0); 471 | } 472 | 473 | button:disabled, 474 | button[disabled] { 475 | border: 1.5px solid var(--border-secondary); 476 | cursor: initial; 477 | opacity: 0.55; 478 | } 479 | 480 | label { 481 | display: block; 482 | max-width: fit-content; 483 | font-weight: 500; 484 | } 485 | 486 | input, 487 | textarea, 488 | select { 489 | font-size: 1em; 490 | background-color: var(--background-muted); 491 | border: 1px solid var(--border-secondary); 492 | color: var(--color-primary); 493 | padding: 0.5rem 0.75rem; 494 | border-radius: var(--border-radius-small); 495 | width: 100%; 496 | /* max-width: fit-content; */ 497 | outline: none; 498 | appearance: none; 499 | } 500 | 501 | input:disabled { 502 | cursor: not-allowed; 503 | opacity: 0.6; 504 | } 505 | 506 | input[type="number"].buttonless { 507 | appearance: textfield; 508 | -moz-appearance: textfield; 509 | } 510 | 511 | input[type="number"].buttonless::-webkit-inner-spin-button { 512 | display: none; 513 | appearance: none; 514 | } 515 | 516 | input[type="number"].buttonless::-webkit-outer-spin-button { 517 | -webkit-appearance: none; 518 | margin: 0; 519 | } 520 | 521 | input[type="checkbox"], 522 | input[type="radio"] { 523 | padding: 0.5rem; 524 | width: fit-content; 525 | vertical-align: middle; 526 | position: relative; 527 | margin: 0.5rem 0.25rem 0.5rem 0.5rem; 528 | /* margin-right: 0.33em; 529 | margin-top: 0.31em; */ 530 | } 531 | 532 | input[type="checkbox"] { 533 | border-radius: 7px; 534 | margin-left: 0; 535 | } 536 | 537 | input[type="radio"] { 538 | border-radius: 100%; 539 | } 540 | 541 | input[type="checkbox"]:checked, 542 | input[type="radio"]:checked { 543 | background: var(--border-secondary); 544 | } 545 | 546 | input[type="range"] { 547 | vertical-align: middle; 548 | padding: 0; 549 | } 550 | 551 | input[type="color"] { 552 | appearance: none; 553 | border: none; 554 | outline-style: none; 555 | padding: initial; 556 | max-width: initial; 557 | height: 2rem; 558 | width: 3rem; 559 | } 560 | 561 | select:is([multiple]) { 562 | background: none; 563 | height: fit-content; 564 | } 565 | 566 | fieldset:focus-within, 567 | input:focus-within, 568 | textarea:focus-within, 569 | select:focus-within { 570 | border-color: var(--border-highlight); 571 | } 572 | 573 | fieldset:hover, 574 | input:hover, 575 | textarea:hover, 576 | select:hover { 577 | border-color: var(--border-highlight); 578 | } 579 | 580 | progress { 581 | appearance: none; 582 | height: 1rem; 583 | margin: 0.75rem 0; 584 | } 585 | 586 | progress::-webkit-progress-bar { 587 | background: var(--background-main); 588 | border: 1px solid var(--border-primary); 589 | border-radius: var(--highlight-border-radius); 590 | } 591 | 592 | progress::-webkit-progress-value { 593 | background-color: var(--color-link); 594 | border-radius: var(--border-radius-small); 595 | } 596 | 597 | progress::-moz-progress-bar { 598 | background-color: var(--color-link); 599 | border-radius: var(--border-radius-small); 600 | } 601 | 602 | fieldset { 603 | border: 1px solid var(--border-primary); 604 | border-radius: var(--border-radius-small); 605 | margin: 0; 606 | margin-bottom: 6px; 607 | padding: 1rem; 608 | max-width: fit-content; 609 | } 610 | 611 | /* Interactive elements */ 612 | details { 613 | border: 1px solid var(--border-primary); 614 | border-radius: var(--border-radius-small); 615 | padding: 0.5rem 0.75rem; 616 | } 617 | 618 | summary { 619 | font-weight: bold; 620 | } 621 | 622 | details[open] summary { 623 | border-bottom: 1px solid var(--border-primary); 624 | margin-bottom: 0.5rem; 625 | } 626 | 627 | .card, 628 | .button-card { 629 | background-color: var(--background-muted); 630 | color: var(--color-primary); 631 | padding: 1rem; 632 | border-radius: 0.5rem; 633 | } 634 | 635 | .card { 636 | border: 1px solid var(--color-link-transparent); 637 | } 638 | 639 | .button-card { 640 | border: 1px solid var(--border-secondary); 641 | } 642 | 643 | .button-card { 644 | box-shadow: none; 645 | } 646 | 647 | .button-card:hover { 648 | transform: translate(0); 649 | box-shadow: none; 650 | border-color: var(--border-highlight); 651 | } 652 | 653 | .card-button-primary, 654 | .card-button-secondary { 655 | border: none !important; 656 | box-shadow: none !important; 657 | } 658 | 659 | .card-button-primary { 660 | color: var(--background-primary); 661 | background: var(--color-link); 662 | } 663 | 664 | .card-button-secondary { 665 | color: var(--color-link); 666 | background: var(--color-link-transparent); 667 | } 668 | 669 | .card-button-primary:hover, 670 | .card-button-secondary:hover { 671 | transform: none; 672 | opacity: 0.6; 673 | } 674 | 675 | 676 | @media (min-width: 640px) { 677 | body { 678 | font-size: 1rem; 679 | line-height: 1.5rem; 680 | } 681 | 682 | /* h1, */ 683 | .text-large { 684 | font-size: 2.25rem; 685 | line-height: 2.5rem; 686 | } 687 | 688 | h1 { 689 | font-size: 1.875rem; 690 | line-height: 2.25rem; 691 | } 692 | 693 | h2 { 694 | font-size: 1.5rem; 695 | line-height: 2rem; 696 | } 697 | 698 | h3, 699 | .text-medium { 700 | font-size: 1.25rem; 701 | line-height: 1.75rem; 702 | } 703 | 704 | h4 { 705 | font-size: 1.125rem; 706 | line-height: 1.75rem; 707 | } 708 | 709 | h5, 710 | h6 { 711 | font-size: 1rem; 712 | line-height: 1.5rem; 713 | } 714 | 715 | button { 716 | width: fit-content; 717 | } 718 | 719 | input { 720 | max-width: 600px; 721 | } 722 | } -------------------------------------------------------------------------------- /svelte-tutorial/src/fanta.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Fanta.css (Fantasy + CSS) 3 | * Version 0.1.0 4 | * https://github.com/jamezmca/fantacss 5 | * 6 | * Sections 7 | * 1. Content sectioning 8 | * 2. Text content 9 | * 3. Inline text semantics 10 | * 4. Image and multimedia 11 | * 5. Tables 12 | * 6. Forms 13 | * 7. Interactive elements 14 | * 15 | */ 16 | 17 | /* @import url('https://fonts.googleapis.com/css2?family=Eczar:wght@400..800&family=Grenze:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); */ 18 | @import url('https://fonts.googleapis.com/css2?family=Eczar:wght@400..800&family=Press+Start+2P&display=swap'); 19 | 20 | /* :root { 21 | --background-primary: white; 22 | --background-secondary: ''; 23 | --background-tertiary: #dbeafe; 24 | --background-accent: ''; 25 | --background-compliment: ''; 26 | --background-gradient: ''; 27 | --background-muted: #f8fafc; 28 | 29 | --color-primary: #030615; 30 | --color-secondary: ''; 31 | --color-tertiary: ''; 32 | --color-accent: ''; 33 | --color-compliment: ''; 34 | --color-gradient: ''; 35 | --color-muted: ; 36 | --color-link: #2563eb; 37 | --color-link-transparent: rgba(37, 99, 235, 0.1); 38 | 39 | --color-success: ''; 40 | --color-warning: ''; 41 | --color-error: ''; 42 | --color-info: ''; 43 | --color-highlight: #fef9c3; 44 | 45 | --gradient-start: #9580ff; 46 | --gradient-end: #80ffea; 47 | 48 | --border-primary: #f1f5f9; 49 | --border-secondary: #bed1e7; 50 | --border-highlight: #64748b; 51 | --border-tertiary: ''; 52 | 53 | --shadow-dark: ''; 54 | --shadow-light: ''; 55 | --shadow-text: ''; 56 | 57 | --padding-small: 1rem; 58 | --padding-large: 2rem; 59 | 60 | --border-radius-small: 0.5rem; 61 | --border-radius-large: 0.75rem; 62 | --highlight-border-radius: 0.5rem; 63 | 64 | --text-selection: ''; 65 | } */ 66 | 67 | /* @media (prefers-color-scheme: dark) { */ 68 | :root { 69 | --background-primary: #030615; 70 | --background-secondary: rgb(23 37 84); 71 | --background-tertiary: #121424; 72 | --background-accent: ''; 73 | --background-compliment: ''; 74 | --background-gradient: ''; 75 | --background-muted: #1a1e32; 76 | 77 | --color-primary: rgb(219 234 254); 78 | --color-secondary: ''; 79 | --color-tertiary: ''; 80 | --color-accent: ''; 81 | --color-compliment: ''; 82 | --color-gradient: ''; 83 | --color-muted: ; 84 | --color-link: #60a5fa; 85 | --color-link-transparent: rgba(37, 99, 235, 0.1); 86 | 87 | --color-success: ''; 88 | --color-warning: ''; 89 | --color-error: ''; 90 | --color-info: ''; 91 | --color-highlight: #fef9c3; 92 | 93 | --gradient-start: #0084ff; 94 | --gradient-end: #00fffb; 95 | 96 | --border-primary: #29325b; 97 | --border-secondary: #93c5fd; 98 | --border-highlight: #4649af; 99 | --border-tertiary: ''; 100 | 101 | --shadow-dark: ''; 102 | --shadow-light: ''; 103 | --shadow-text: ''; 104 | 105 | --padding-small: 1rem; 106 | --padding-large: 2rem; 107 | 108 | --border-radius-small: 0.5rem; 109 | --border-radius-large: 0.75rem; 110 | --highlight-border-radius: 0.5rem; 111 | 112 | --text-selection: ''; 113 | } 114 | 115 | /* } */ 116 | 117 | * { 118 | margin: 0; 119 | padding: 0; 120 | box-sizing: border-box; 121 | font-family: "Eczar", serif; 122 | } 123 | 124 | body { 125 | background: var(--background-primary); 126 | color: var(--color-primary); 127 | font-size: 0.875rem; 128 | line-height: 1.6rem; 129 | } 130 | 131 | /* Special */ 132 | .text-gradient { 133 | -webkit-text-fill-color: transparent; 134 | -webkit-background-clip: text; 135 | background-image: linear-gradient(180deg, var(--gradient-start) 0, var(--gradient-end) 100%); 136 | background-size: 100%; 137 | -webkit-box-decoration-break: clone; 138 | } 139 | 140 | 141 | /* Typography */ 142 | h1, 143 | h2, 144 | h3, 145 | h4, 146 | h5, 147 | h6, 148 | button { 149 | font-family: "Press Start 2P", serif; 150 | width: fit-content; 151 | } 152 | 153 | /* h1, */ 154 | .text-large { 155 | font-size: 1.875rem; 156 | line-height: 2.25rem; 157 | } 158 | 159 | h1 { 160 | font-size: 1.5rem; 161 | line-height: 2rem; 162 | } 163 | 164 | .special-shadow { 165 | text-shadow: -1px -1px 4px #0022ffbf, 2px 2px 10px #0022ffbf, 0 0 20px #0022ffbf; 166 | color: white; 167 | } 168 | 169 | h2 { 170 | font-size: 1.25rem; 171 | line-height: 1.75rem; 172 | } 173 | 174 | h3, 175 | .text-medium { 176 | font-size: 1.125rem; 177 | line-height: 1.75rem; 178 | } 179 | 180 | h4 { 181 | font-size: 1rem; 182 | line-height: 1.5rem; 183 | } 184 | 185 | h5, 186 | h6 { 187 | font-size: 0.875rem; 188 | line-height: 1.25rem; 189 | } 190 | 191 | p { 192 | display: block; 193 | width: fit-content; 194 | } 195 | 196 | span { 197 | font-weight: inherit; 198 | font-size: inherit; 199 | line-height: inherit; 200 | } 201 | 202 | /* Content Sectioning */ 203 | 204 | main {} 205 | 206 | header {} 207 | 208 | footer {} 209 | 210 | nav {} 211 | 212 | section {} 213 | 214 | address { 215 | font-style: normal; 216 | } 217 | 218 | aside { 219 | float: right; 220 | width: 40%; 221 | padding: 0.75rem; 222 | margin: 0.5rem; 223 | font-style: italic; 224 | color: var(--color-primary); 225 | background-color: var(--background-muted); 226 | border-radius: var(--border-radius-large); 227 | } 228 | 229 | 230 | /* Text Content */ 231 | blockquote { 232 | position: relative; 233 | padding-left: 1.5rem; 234 | margin: 0; 235 | } 236 | 237 | blockquote::after { 238 | content: ""; 239 | display: block; 240 | position: absolute; 241 | left: 0; 242 | top: 0; 243 | height: 100%; 244 | border-left: 7px solid var(--border-primary); 245 | border-radius: 6px; 246 | } 247 | 248 | blockquote footer { 249 | padding-top: 1rem; 250 | } 251 | 252 | dd { 253 | padding-bottom: 11px; 254 | } 255 | 256 | dl {} 257 | 258 | dt { 259 | font-weight: bold; 260 | } 261 | 262 | figure {} 263 | 264 | figcaption { 265 | padding-top: 10px; 266 | font-size: 0.8rem; 267 | } 268 | 269 | hr {} 270 | 271 | 272 | ul, 273 | ol { 274 | list-style-position: inside; 275 | padding-left: 1rem; 276 | } 277 | 278 | li { 279 | line-height: 1.6em; 280 | } 281 | 282 | /* Inline Text Elements */ 283 | 284 | a { 285 | color: var(--color-link); 286 | } 287 | 288 | a:active, 289 | a:focus, 290 | a:hover { 291 | text-decoration: none; 292 | } 293 | 294 | .link-button { 295 | border: none; 296 | color: var(--color-primary); 297 | background: var(--background-primary); 298 | box-shadow: none; 299 | padding: 0.5rem 1rem; 300 | } 301 | 302 | .link-button:hover { 303 | box-shadow: none; 304 | transform: unset; 305 | text-decoration: underline; 306 | } 307 | 308 | mark, 309 | samp, 310 | kbd, 311 | code, 312 | time { 313 | border-radius: var(--highlight-border-radius, 4px); 314 | box-decoration-break: clone; 315 | -webkit-box-decoration-break: clone; 316 | } 317 | 318 | mark { 319 | background-color: var(--color-highlight); 320 | padding: 0 4px; 321 | } 322 | 323 | samp { 324 | font-weight: bold; 325 | padding: 0.5rem 1rem; 326 | background-color: var(--background-muted); 327 | color: var(--color-primary); 328 | } 329 | 330 | kbd, 331 | time { 332 | padding: 0rem 0.5rem; 333 | background-color: var(--background-muted); 334 | color: var(--color-primary); 335 | } 336 | 337 | code, 338 | pre { 339 | font-size: 0.9em; 340 | padding: 0.2rem 0.5rem; 341 | background: var(--background-muted); 342 | border: 1px solid var(--border-primary); 343 | max-width: fit-content; 344 | overflow-x: auto; 345 | } 346 | 347 | pre>code { 348 | padding: 10px; 349 | border: 0; 350 | display: block; 351 | overflow-x: auto; 352 | } 353 | 354 | pre { 355 | border-radius: var(--border-radius-large); 356 | } 357 | 358 | sup, 359 | sub { 360 | line-height: normal; 361 | } 362 | 363 | /* Image and multimedia */ 364 | audio { 365 | width: 100%; 366 | } 367 | 368 | audio, 369 | img, 370 | video { 371 | border-radius: var(--border-radius-large); 372 | max-width: 100%; 373 | } 374 | 375 | img { 376 | height: auto; 377 | } 378 | 379 | /* Tables */ 380 | table { 381 | width: fit-content; 382 | border: 1px solid var(--border-primary); 383 | background: var(--background-muted); 384 | border-radius: var(--border-radius-small); 385 | } 386 | 387 | table tr:last-child td:first-child { 388 | border-bottom-left-radius: 8px; 389 | } 390 | 391 | table tr:last-child td:last-child { 392 | border-bottom-right-radius: 8px; 393 | } 394 | 395 | table tr:first-child th:first-child { 396 | border-top-left-radius: 8px; 397 | } 398 | 399 | table tr:first-child th:last-child { 400 | border-top-right-radius: 8px; 401 | } 402 | 403 | th { 404 | background-color: var(--background-muted); 405 | } 406 | 407 | td { 408 | background: var(--background-primary); 409 | } 410 | 411 | td, 412 | th { 413 | text-align: left; 414 | padding: 8px; 415 | } 416 | 417 | thead { 418 | border-collapse: collapse; 419 | } 420 | 421 | tfoot { 422 | border-top: 1px solid black; 423 | } 424 | 425 | table tr:hover td, 426 | tbody tr:nth-child(even):hover td { 427 | background-color: var(--background-muted); 428 | } 429 | 430 | /* Form elements */ 431 | input, 432 | button, 433 | select, 434 | optgroup, 435 | textarea { 436 | /* margin: 0; */ 437 | } 438 | 439 | button, 440 | select, 441 | input[type="submit"], 442 | input[type="button"], 443 | input[type="checkbox"], 444 | input[type="range"], 445 | input[type="radio"] { 446 | cursor: pointer; 447 | } 448 | 449 | button { 450 | color: var(--color-primary); 451 | background-color: var(--background-secondary); 452 | width: fit-content; 453 | font-family: inherit; 454 | font-size: inherit; 455 | font-weight: 500; 456 | padding: 0.25rem 1.25rem; 457 | border: 1.5px solid var(--border-secondary); 458 | border-radius: var(--border-radius-small); 459 | box-shadow: 2px 2px 0 0 var(--border-secondary); 460 | transition-duration: 200ms; 461 | } 462 | 463 | button:hover { 464 | box-shadow: 0 0 0 0 var(--border-secondary); 465 | transform: translate(2px, 2px); 466 | } 467 | 468 | button[disabled]:hover { 469 | box-shadow: 2px 2px 0 0 var(--border-secondary); 470 | transform: translate(0, 0); 471 | } 472 | 473 | button:disabled, 474 | button[disabled] { 475 | border: 1.5px solid var(--border-secondary); 476 | cursor: initial; 477 | opacity: 0.55; 478 | } 479 | 480 | label { 481 | display: block; 482 | max-width: fit-content; 483 | font-weight: 500; 484 | } 485 | 486 | input, 487 | textarea, 488 | select { 489 | font-size: 1em; 490 | background-color: var(--background-muted); 491 | border: 1px solid var(--border-secondary); 492 | color: var(--color-primary); 493 | padding: 0.5rem 0.75rem; 494 | border-radius: var(--border-radius-small); 495 | width: 100%; 496 | /* max-width: fit-content; */ 497 | outline: none; 498 | appearance: none; 499 | } 500 | 501 | input:disabled { 502 | cursor: not-allowed; 503 | opacity: 0.6; 504 | } 505 | 506 | input[type="number"].buttonless { 507 | appearance: textfield; 508 | -moz-appearance: textfield; 509 | } 510 | 511 | input[type="number"].buttonless::-webkit-inner-spin-button { 512 | display: none; 513 | appearance: none; 514 | } 515 | 516 | input[type="number"].buttonless::-webkit-outer-spin-button { 517 | -webkit-appearance: none; 518 | margin: 0; 519 | } 520 | 521 | input[type="checkbox"], 522 | input[type="radio"] { 523 | padding: 0.5rem; 524 | width: fit-content; 525 | vertical-align: middle; 526 | position: relative; 527 | margin: 0.5rem 0.25rem 0.5rem 0.5rem; 528 | /* margin-right: 0.33em; 529 | margin-top: 0.31em; */ 530 | } 531 | 532 | input[type="checkbox"] { 533 | border-radius: 7px; 534 | margin-left: 0; 535 | } 536 | 537 | input[type="radio"] { 538 | border-radius: 100%; 539 | } 540 | 541 | input[type="checkbox"]:checked, 542 | input[type="radio"]:checked { 543 | background: var(--border-secondary); 544 | } 545 | 546 | input[type="range"] { 547 | vertical-align: middle; 548 | padding: 0; 549 | } 550 | 551 | input[type="color"] { 552 | appearance: none; 553 | border: none; 554 | outline-style: none; 555 | padding: initial; 556 | max-width: initial; 557 | height: 2rem; 558 | width: 3rem; 559 | } 560 | 561 | select:is([multiple]) { 562 | background: none; 563 | height: fit-content; 564 | } 565 | 566 | fieldset:focus-within, 567 | input:focus-within, 568 | textarea:focus-within, 569 | select:focus-within { 570 | border-color: var(--border-highlight); 571 | } 572 | 573 | fieldset:hover, 574 | input:hover, 575 | textarea:hover, 576 | select:hover { 577 | border-color: var(--border-highlight); 578 | } 579 | 580 | progress { 581 | appearance: none; 582 | height: 1rem; 583 | margin: 0.75rem 0; 584 | } 585 | 586 | progress::-webkit-progress-bar { 587 | background: var(--background-main); 588 | border: 1px solid var(--border-primary); 589 | border-radius: var(--highlight-border-radius); 590 | } 591 | 592 | progress::-webkit-progress-value { 593 | background-color: var(--color-link); 594 | border-radius: var(--border-radius-small); 595 | } 596 | 597 | progress::-moz-progress-bar { 598 | background-color: var(--color-link); 599 | border-radius: var(--border-radius-small); 600 | } 601 | 602 | fieldset { 603 | border: 1px solid var(--border-primary); 604 | border-radius: var(--border-radius-small); 605 | margin: 0; 606 | margin-bottom: 6px; 607 | padding: 1rem; 608 | max-width: fit-content; 609 | } 610 | 611 | /* Interactive elements */ 612 | details { 613 | border: 1px solid var(--border-primary); 614 | border-radius: var(--border-radius-small); 615 | padding: 0.5rem 0.75rem; 616 | } 617 | 618 | summary { 619 | font-weight: bold; 620 | } 621 | 622 | details[open] summary { 623 | border-bottom: 1px solid var(--border-primary); 624 | margin-bottom: 0.5rem; 625 | } 626 | 627 | .card, 628 | .button-card { 629 | background-color: var(--background-muted); 630 | color: var(--color-primary); 631 | padding: 1rem; 632 | border-radius: 0.5rem; 633 | } 634 | 635 | .card { 636 | border: 1px solid var(--color-link-transparent); 637 | } 638 | 639 | .button-card { 640 | border: 1px solid var(--border-secondary); 641 | } 642 | 643 | .button-card { 644 | box-shadow: none; 645 | } 646 | 647 | .button-card:hover { 648 | transform: translate(0); 649 | box-shadow: none; 650 | border-color: var(--border-highlight); 651 | } 652 | 653 | .card-button-primary, 654 | .card-button-secondary { 655 | border: none !important; 656 | box-shadow: none !important; 657 | } 658 | 659 | .card-button-primary { 660 | color: var(--background-primary); 661 | background: var(--color-link); 662 | } 663 | 664 | .card-button-secondary { 665 | color: var(--color-link); 666 | background: var(--color-link-transparent); 667 | } 668 | 669 | .card-button-primary:hover, 670 | .card-button-secondary:hover { 671 | transform: none; 672 | opacity: 0.6; 673 | } 674 | 675 | 676 | @media (min-width: 640px) { 677 | body { 678 | font-size: 1rem; 679 | line-height: 1.5rem; 680 | } 681 | 682 | /* h1, */ 683 | .text-large { 684 | font-size: 2.25rem; 685 | line-height: 2.5rem; 686 | } 687 | 688 | h1 { 689 | font-size: 1.875rem; 690 | line-height: 2.25rem; 691 | } 692 | 693 | h2 { 694 | font-size: 1.5rem; 695 | line-height: 2rem; 696 | } 697 | 698 | h3, 699 | .text-medium { 700 | font-size: 1.25rem; 701 | line-height: 1.75rem; 702 | } 703 | 704 | h4 { 705 | font-size: 1.125rem; 706 | line-height: 1.75rem; 707 | } 708 | 709 | h5, 710 | h6 { 711 | font-size: 1rem; 712 | line-height: 1.5rem; 713 | } 714 | 715 | button { 716 | width: fit-content; 717 | } 718 | 719 | input { 720 | max-width: 600px; 721 | } 722 | } -------------------------------------------------------------------------------- /vue-tutorial/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-tutorial", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "vue-tutorial", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "vue": "^3.5.13" 12 | }, 13 | "devDependencies": { 14 | "@vitejs/plugin-vue": "^5.2.1", 15 | "vite": "^6.2.0" 16 | } 17 | }, 18 | "node_modules/@babel/helper-string-parser": { 19 | "version": "7.25.9", 20 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", 21 | "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", 22 | "engines": { 23 | "node": ">=6.9.0" 24 | } 25 | }, 26 | "node_modules/@babel/helper-validator-identifier": { 27 | "version": "7.25.9", 28 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", 29 | "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", 30 | "engines": { 31 | "node": ">=6.9.0" 32 | } 33 | }, 34 | "node_modules/@babel/parser": { 35 | "version": "7.27.0", 36 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", 37 | "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", 38 | "dependencies": { 39 | "@babel/types": "^7.27.0" 40 | }, 41 | "bin": { 42 | "parser": "bin/babel-parser.js" 43 | }, 44 | "engines": { 45 | "node": ">=6.0.0" 46 | } 47 | }, 48 | "node_modules/@babel/types": { 49 | "version": "7.27.0", 50 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", 51 | "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", 52 | "dependencies": { 53 | "@babel/helper-string-parser": "^7.25.9", 54 | "@babel/helper-validator-identifier": "^7.25.9" 55 | }, 56 | "engines": { 57 | "node": ">=6.9.0" 58 | } 59 | }, 60 | "node_modules/@esbuild/aix-ppc64": { 61 | "version": "0.25.2", 62 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", 63 | "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", 64 | "cpu": [ 65 | "ppc64" 66 | ], 67 | "dev": true, 68 | "optional": true, 69 | "os": [ 70 | "aix" 71 | ], 72 | "engines": { 73 | "node": ">=18" 74 | } 75 | }, 76 | "node_modules/@esbuild/android-arm": { 77 | "version": "0.25.2", 78 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", 79 | "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", 80 | "cpu": [ 81 | "arm" 82 | ], 83 | "dev": true, 84 | "optional": true, 85 | "os": [ 86 | "android" 87 | ], 88 | "engines": { 89 | "node": ">=18" 90 | } 91 | }, 92 | "node_modules/@esbuild/android-arm64": { 93 | "version": "0.25.2", 94 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", 95 | "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", 96 | "cpu": [ 97 | "arm64" 98 | ], 99 | "dev": true, 100 | "optional": true, 101 | "os": [ 102 | "android" 103 | ], 104 | "engines": { 105 | "node": ">=18" 106 | } 107 | }, 108 | "node_modules/@esbuild/android-x64": { 109 | "version": "0.25.2", 110 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", 111 | "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", 112 | "cpu": [ 113 | "x64" 114 | ], 115 | "dev": true, 116 | "optional": true, 117 | "os": [ 118 | "android" 119 | ], 120 | "engines": { 121 | "node": ">=18" 122 | } 123 | }, 124 | "node_modules/@esbuild/darwin-arm64": { 125 | "version": "0.25.2", 126 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", 127 | "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", 128 | "cpu": [ 129 | "arm64" 130 | ], 131 | "dev": true, 132 | "optional": true, 133 | "os": [ 134 | "darwin" 135 | ], 136 | "engines": { 137 | "node": ">=18" 138 | } 139 | }, 140 | "node_modules/@esbuild/darwin-x64": { 141 | "version": "0.25.2", 142 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", 143 | "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", 144 | "cpu": [ 145 | "x64" 146 | ], 147 | "dev": true, 148 | "optional": true, 149 | "os": [ 150 | "darwin" 151 | ], 152 | "engines": { 153 | "node": ">=18" 154 | } 155 | }, 156 | "node_modules/@esbuild/freebsd-arm64": { 157 | "version": "0.25.2", 158 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", 159 | "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", 160 | "cpu": [ 161 | "arm64" 162 | ], 163 | "dev": true, 164 | "optional": true, 165 | "os": [ 166 | "freebsd" 167 | ], 168 | "engines": { 169 | "node": ">=18" 170 | } 171 | }, 172 | "node_modules/@esbuild/freebsd-x64": { 173 | "version": "0.25.2", 174 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", 175 | "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", 176 | "cpu": [ 177 | "x64" 178 | ], 179 | "dev": true, 180 | "optional": true, 181 | "os": [ 182 | "freebsd" 183 | ], 184 | "engines": { 185 | "node": ">=18" 186 | } 187 | }, 188 | "node_modules/@esbuild/linux-arm": { 189 | "version": "0.25.2", 190 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", 191 | "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", 192 | "cpu": [ 193 | "arm" 194 | ], 195 | "dev": true, 196 | "optional": true, 197 | "os": [ 198 | "linux" 199 | ], 200 | "engines": { 201 | "node": ">=18" 202 | } 203 | }, 204 | "node_modules/@esbuild/linux-arm64": { 205 | "version": "0.25.2", 206 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", 207 | "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", 208 | "cpu": [ 209 | "arm64" 210 | ], 211 | "dev": true, 212 | "optional": true, 213 | "os": [ 214 | "linux" 215 | ], 216 | "engines": { 217 | "node": ">=18" 218 | } 219 | }, 220 | "node_modules/@esbuild/linux-ia32": { 221 | "version": "0.25.2", 222 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", 223 | "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", 224 | "cpu": [ 225 | "ia32" 226 | ], 227 | "dev": true, 228 | "optional": true, 229 | "os": [ 230 | "linux" 231 | ], 232 | "engines": { 233 | "node": ">=18" 234 | } 235 | }, 236 | "node_modules/@esbuild/linux-loong64": { 237 | "version": "0.25.2", 238 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", 239 | "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", 240 | "cpu": [ 241 | "loong64" 242 | ], 243 | "dev": true, 244 | "optional": true, 245 | "os": [ 246 | "linux" 247 | ], 248 | "engines": { 249 | "node": ">=18" 250 | } 251 | }, 252 | "node_modules/@esbuild/linux-mips64el": { 253 | "version": "0.25.2", 254 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", 255 | "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", 256 | "cpu": [ 257 | "mips64el" 258 | ], 259 | "dev": true, 260 | "optional": true, 261 | "os": [ 262 | "linux" 263 | ], 264 | "engines": { 265 | "node": ">=18" 266 | } 267 | }, 268 | "node_modules/@esbuild/linux-ppc64": { 269 | "version": "0.25.2", 270 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", 271 | "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", 272 | "cpu": [ 273 | "ppc64" 274 | ], 275 | "dev": true, 276 | "optional": true, 277 | "os": [ 278 | "linux" 279 | ], 280 | "engines": { 281 | "node": ">=18" 282 | } 283 | }, 284 | "node_modules/@esbuild/linux-riscv64": { 285 | "version": "0.25.2", 286 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", 287 | "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", 288 | "cpu": [ 289 | "riscv64" 290 | ], 291 | "dev": true, 292 | "optional": true, 293 | "os": [ 294 | "linux" 295 | ], 296 | "engines": { 297 | "node": ">=18" 298 | } 299 | }, 300 | "node_modules/@esbuild/linux-s390x": { 301 | "version": "0.25.2", 302 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", 303 | "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", 304 | "cpu": [ 305 | "s390x" 306 | ], 307 | "dev": true, 308 | "optional": true, 309 | "os": [ 310 | "linux" 311 | ], 312 | "engines": { 313 | "node": ">=18" 314 | } 315 | }, 316 | "node_modules/@esbuild/linux-x64": { 317 | "version": "0.25.2", 318 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", 319 | "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", 320 | "cpu": [ 321 | "x64" 322 | ], 323 | "dev": true, 324 | "optional": true, 325 | "os": [ 326 | "linux" 327 | ], 328 | "engines": { 329 | "node": ">=18" 330 | } 331 | }, 332 | "node_modules/@esbuild/netbsd-arm64": { 333 | "version": "0.25.2", 334 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", 335 | "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", 336 | "cpu": [ 337 | "arm64" 338 | ], 339 | "dev": true, 340 | "optional": true, 341 | "os": [ 342 | "netbsd" 343 | ], 344 | "engines": { 345 | "node": ">=18" 346 | } 347 | }, 348 | "node_modules/@esbuild/netbsd-x64": { 349 | "version": "0.25.2", 350 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", 351 | "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", 352 | "cpu": [ 353 | "x64" 354 | ], 355 | "dev": true, 356 | "optional": true, 357 | "os": [ 358 | "netbsd" 359 | ], 360 | "engines": { 361 | "node": ">=18" 362 | } 363 | }, 364 | "node_modules/@esbuild/openbsd-arm64": { 365 | "version": "0.25.2", 366 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", 367 | "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", 368 | "cpu": [ 369 | "arm64" 370 | ], 371 | "dev": true, 372 | "optional": true, 373 | "os": [ 374 | "openbsd" 375 | ], 376 | "engines": { 377 | "node": ">=18" 378 | } 379 | }, 380 | "node_modules/@esbuild/openbsd-x64": { 381 | "version": "0.25.2", 382 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", 383 | "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", 384 | "cpu": [ 385 | "x64" 386 | ], 387 | "dev": true, 388 | "optional": true, 389 | "os": [ 390 | "openbsd" 391 | ], 392 | "engines": { 393 | "node": ">=18" 394 | } 395 | }, 396 | "node_modules/@esbuild/sunos-x64": { 397 | "version": "0.25.2", 398 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", 399 | "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", 400 | "cpu": [ 401 | "x64" 402 | ], 403 | "dev": true, 404 | "optional": true, 405 | "os": [ 406 | "sunos" 407 | ], 408 | "engines": { 409 | "node": ">=18" 410 | } 411 | }, 412 | "node_modules/@esbuild/win32-arm64": { 413 | "version": "0.25.2", 414 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", 415 | "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", 416 | "cpu": [ 417 | "arm64" 418 | ], 419 | "dev": true, 420 | "optional": true, 421 | "os": [ 422 | "win32" 423 | ], 424 | "engines": { 425 | "node": ">=18" 426 | } 427 | }, 428 | "node_modules/@esbuild/win32-ia32": { 429 | "version": "0.25.2", 430 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", 431 | "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", 432 | "cpu": [ 433 | "ia32" 434 | ], 435 | "dev": true, 436 | "optional": true, 437 | "os": [ 438 | "win32" 439 | ], 440 | "engines": { 441 | "node": ">=18" 442 | } 443 | }, 444 | "node_modules/@esbuild/win32-x64": { 445 | "version": "0.25.2", 446 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", 447 | "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", 448 | "cpu": [ 449 | "x64" 450 | ], 451 | "dev": true, 452 | "optional": true, 453 | "os": [ 454 | "win32" 455 | ], 456 | "engines": { 457 | "node": ">=18" 458 | } 459 | }, 460 | "node_modules/@jridgewell/sourcemap-codec": { 461 | "version": "1.5.0", 462 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 463 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" 464 | }, 465 | "node_modules/@rollup/rollup-android-arm-eabi": { 466 | "version": "4.39.0", 467 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", 468 | "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", 469 | "cpu": [ 470 | "arm" 471 | ], 472 | "dev": true, 473 | "optional": true, 474 | "os": [ 475 | "android" 476 | ] 477 | }, 478 | "node_modules/@rollup/rollup-android-arm64": { 479 | "version": "4.39.0", 480 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", 481 | "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", 482 | "cpu": [ 483 | "arm64" 484 | ], 485 | "dev": true, 486 | "optional": true, 487 | "os": [ 488 | "android" 489 | ] 490 | }, 491 | "node_modules/@rollup/rollup-darwin-arm64": { 492 | "version": "4.39.0", 493 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", 494 | "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", 495 | "cpu": [ 496 | "arm64" 497 | ], 498 | "dev": true, 499 | "optional": true, 500 | "os": [ 501 | "darwin" 502 | ] 503 | }, 504 | "node_modules/@rollup/rollup-darwin-x64": { 505 | "version": "4.39.0", 506 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", 507 | "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", 508 | "cpu": [ 509 | "x64" 510 | ], 511 | "dev": true, 512 | "optional": true, 513 | "os": [ 514 | "darwin" 515 | ] 516 | }, 517 | "node_modules/@rollup/rollup-freebsd-arm64": { 518 | "version": "4.39.0", 519 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", 520 | "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", 521 | "cpu": [ 522 | "arm64" 523 | ], 524 | "dev": true, 525 | "optional": true, 526 | "os": [ 527 | "freebsd" 528 | ] 529 | }, 530 | "node_modules/@rollup/rollup-freebsd-x64": { 531 | "version": "4.39.0", 532 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", 533 | "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", 534 | "cpu": [ 535 | "x64" 536 | ], 537 | "dev": true, 538 | "optional": true, 539 | "os": [ 540 | "freebsd" 541 | ] 542 | }, 543 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 544 | "version": "4.39.0", 545 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", 546 | "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", 547 | "cpu": [ 548 | "arm" 549 | ], 550 | "dev": true, 551 | "optional": true, 552 | "os": [ 553 | "linux" 554 | ] 555 | }, 556 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 557 | "version": "4.39.0", 558 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", 559 | "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", 560 | "cpu": [ 561 | "arm" 562 | ], 563 | "dev": true, 564 | "optional": true, 565 | "os": [ 566 | "linux" 567 | ] 568 | }, 569 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 570 | "version": "4.39.0", 571 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", 572 | "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", 573 | "cpu": [ 574 | "arm64" 575 | ], 576 | "dev": true, 577 | "optional": true, 578 | "os": [ 579 | "linux" 580 | ] 581 | }, 582 | "node_modules/@rollup/rollup-linux-arm64-musl": { 583 | "version": "4.39.0", 584 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", 585 | "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", 586 | "cpu": [ 587 | "arm64" 588 | ], 589 | "dev": true, 590 | "optional": true, 591 | "os": [ 592 | "linux" 593 | ] 594 | }, 595 | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 596 | "version": "4.39.0", 597 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", 598 | "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", 599 | "cpu": [ 600 | "loong64" 601 | ], 602 | "dev": true, 603 | "optional": true, 604 | "os": [ 605 | "linux" 606 | ] 607 | }, 608 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 609 | "version": "4.39.0", 610 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", 611 | "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", 612 | "cpu": [ 613 | "ppc64" 614 | ], 615 | "dev": true, 616 | "optional": true, 617 | "os": [ 618 | "linux" 619 | ] 620 | }, 621 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 622 | "version": "4.39.0", 623 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", 624 | "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", 625 | "cpu": [ 626 | "riscv64" 627 | ], 628 | "dev": true, 629 | "optional": true, 630 | "os": [ 631 | "linux" 632 | ] 633 | }, 634 | "node_modules/@rollup/rollup-linux-riscv64-musl": { 635 | "version": "4.39.0", 636 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", 637 | "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", 638 | "cpu": [ 639 | "riscv64" 640 | ], 641 | "dev": true, 642 | "optional": true, 643 | "os": [ 644 | "linux" 645 | ] 646 | }, 647 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 648 | "version": "4.39.0", 649 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", 650 | "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", 651 | "cpu": [ 652 | "s390x" 653 | ], 654 | "dev": true, 655 | "optional": true, 656 | "os": [ 657 | "linux" 658 | ] 659 | }, 660 | "node_modules/@rollup/rollup-linux-x64-gnu": { 661 | "version": "4.39.0", 662 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", 663 | "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", 664 | "cpu": [ 665 | "x64" 666 | ], 667 | "dev": true, 668 | "optional": true, 669 | "os": [ 670 | "linux" 671 | ] 672 | }, 673 | "node_modules/@rollup/rollup-linux-x64-musl": { 674 | "version": "4.39.0", 675 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", 676 | "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", 677 | "cpu": [ 678 | "x64" 679 | ], 680 | "dev": true, 681 | "optional": true, 682 | "os": [ 683 | "linux" 684 | ] 685 | }, 686 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 687 | "version": "4.39.0", 688 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", 689 | "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", 690 | "cpu": [ 691 | "arm64" 692 | ], 693 | "dev": true, 694 | "optional": true, 695 | "os": [ 696 | "win32" 697 | ] 698 | }, 699 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 700 | "version": "4.39.0", 701 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", 702 | "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", 703 | "cpu": [ 704 | "ia32" 705 | ], 706 | "dev": true, 707 | "optional": true, 708 | "os": [ 709 | "win32" 710 | ] 711 | }, 712 | "node_modules/@rollup/rollup-win32-x64-msvc": { 713 | "version": "4.39.0", 714 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", 715 | "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", 716 | "cpu": [ 717 | "x64" 718 | ], 719 | "dev": true, 720 | "optional": true, 721 | "os": [ 722 | "win32" 723 | ] 724 | }, 725 | "node_modules/@types/estree": { 726 | "version": "1.0.7", 727 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", 728 | "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", 729 | "dev": true 730 | }, 731 | "node_modules/@vitejs/plugin-vue": { 732 | "version": "5.2.3", 733 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", 734 | "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", 735 | "dev": true, 736 | "engines": { 737 | "node": "^18.0.0 || >=20.0.0" 738 | }, 739 | "peerDependencies": { 740 | "vite": "^5.0.0 || ^6.0.0", 741 | "vue": "^3.2.25" 742 | } 743 | }, 744 | "node_modules/@vue/compiler-core": { 745 | "version": "3.5.13", 746 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", 747 | "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", 748 | "dependencies": { 749 | "@babel/parser": "^7.25.3", 750 | "@vue/shared": "3.5.13", 751 | "entities": "^4.5.0", 752 | "estree-walker": "^2.0.2", 753 | "source-map-js": "^1.2.0" 754 | } 755 | }, 756 | "node_modules/@vue/compiler-dom": { 757 | "version": "3.5.13", 758 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", 759 | "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", 760 | "dependencies": { 761 | "@vue/compiler-core": "3.5.13", 762 | "@vue/shared": "3.5.13" 763 | } 764 | }, 765 | "node_modules/@vue/compiler-sfc": { 766 | "version": "3.5.13", 767 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", 768 | "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", 769 | "dependencies": { 770 | "@babel/parser": "^7.25.3", 771 | "@vue/compiler-core": "3.5.13", 772 | "@vue/compiler-dom": "3.5.13", 773 | "@vue/compiler-ssr": "3.5.13", 774 | "@vue/shared": "3.5.13", 775 | "estree-walker": "^2.0.2", 776 | "magic-string": "^0.30.11", 777 | "postcss": "^8.4.48", 778 | "source-map-js": "^1.2.0" 779 | } 780 | }, 781 | "node_modules/@vue/compiler-ssr": { 782 | "version": "3.5.13", 783 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", 784 | "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", 785 | "dependencies": { 786 | "@vue/compiler-dom": "3.5.13", 787 | "@vue/shared": "3.5.13" 788 | } 789 | }, 790 | "node_modules/@vue/reactivity": { 791 | "version": "3.5.13", 792 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", 793 | "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", 794 | "dependencies": { 795 | "@vue/shared": "3.5.13" 796 | } 797 | }, 798 | "node_modules/@vue/runtime-core": { 799 | "version": "3.5.13", 800 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", 801 | "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", 802 | "dependencies": { 803 | "@vue/reactivity": "3.5.13", 804 | "@vue/shared": "3.5.13" 805 | } 806 | }, 807 | "node_modules/@vue/runtime-dom": { 808 | "version": "3.5.13", 809 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", 810 | "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", 811 | "dependencies": { 812 | "@vue/reactivity": "3.5.13", 813 | "@vue/runtime-core": "3.5.13", 814 | "@vue/shared": "3.5.13", 815 | "csstype": "^3.1.3" 816 | } 817 | }, 818 | "node_modules/@vue/server-renderer": { 819 | "version": "3.5.13", 820 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", 821 | "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", 822 | "dependencies": { 823 | "@vue/compiler-ssr": "3.5.13", 824 | "@vue/shared": "3.5.13" 825 | }, 826 | "peerDependencies": { 827 | "vue": "3.5.13" 828 | } 829 | }, 830 | "node_modules/@vue/shared": { 831 | "version": "3.5.13", 832 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", 833 | "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" 834 | }, 835 | "node_modules/csstype": { 836 | "version": "3.1.3", 837 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 838 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" 839 | }, 840 | "node_modules/entities": { 841 | "version": "4.5.0", 842 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 843 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 844 | "engines": { 845 | "node": ">=0.12" 846 | }, 847 | "funding": { 848 | "url": "https://github.com/fb55/entities?sponsor=1" 849 | } 850 | }, 851 | "node_modules/esbuild": { 852 | "version": "0.25.2", 853 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", 854 | "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", 855 | "dev": true, 856 | "hasInstallScript": true, 857 | "bin": { 858 | "esbuild": "bin/esbuild" 859 | }, 860 | "engines": { 861 | "node": ">=18" 862 | }, 863 | "optionalDependencies": { 864 | "@esbuild/aix-ppc64": "0.25.2", 865 | "@esbuild/android-arm": "0.25.2", 866 | "@esbuild/android-arm64": "0.25.2", 867 | "@esbuild/android-x64": "0.25.2", 868 | "@esbuild/darwin-arm64": "0.25.2", 869 | "@esbuild/darwin-x64": "0.25.2", 870 | "@esbuild/freebsd-arm64": "0.25.2", 871 | "@esbuild/freebsd-x64": "0.25.2", 872 | "@esbuild/linux-arm": "0.25.2", 873 | "@esbuild/linux-arm64": "0.25.2", 874 | "@esbuild/linux-ia32": "0.25.2", 875 | "@esbuild/linux-loong64": "0.25.2", 876 | "@esbuild/linux-mips64el": "0.25.2", 877 | "@esbuild/linux-ppc64": "0.25.2", 878 | "@esbuild/linux-riscv64": "0.25.2", 879 | "@esbuild/linux-s390x": "0.25.2", 880 | "@esbuild/linux-x64": "0.25.2", 881 | "@esbuild/netbsd-arm64": "0.25.2", 882 | "@esbuild/netbsd-x64": "0.25.2", 883 | "@esbuild/openbsd-arm64": "0.25.2", 884 | "@esbuild/openbsd-x64": "0.25.2", 885 | "@esbuild/sunos-x64": "0.25.2", 886 | "@esbuild/win32-arm64": "0.25.2", 887 | "@esbuild/win32-ia32": "0.25.2", 888 | "@esbuild/win32-x64": "0.25.2" 889 | } 890 | }, 891 | "node_modules/estree-walker": { 892 | "version": "2.0.2", 893 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 894 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 895 | }, 896 | "node_modules/fsevents": { 897 | "version": "2.3.3", 898 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 899 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 900 | "dev": true, 901 | "hasInstallScript": true, 902 | "optional": true, 903 | "os": [ 904 | "darwin" 905 | ], 906 | "engines": { 907 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 908 | } 909 | }, 910 | "node_modules/magic-string": { 911 | "version": "0.30.17", 912 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 913 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 914 | "dependencies": { 915 | "@jridgewell/sourcemap-codec": "^1.5.0" 916 | } 917 | }, 918 | "node_modules/nanoid": { 919 | "version": "3.3.11", 920 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 921 | "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 922 | "funding": [ 923 | { 924 | "type": "github", 925 | "url": "https://github.com/sponsors/ai" 926 | } 927 | ], 928 | "bin": { 929 | "nanoid": "bin/nanoid.cjs" 930 | }, 931 | "engines": { 932 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 933 | } 934 | }, 935 | "node_modules/picocolors": { 936 | "version": "1.1.1", 937 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 938 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 939 | }, 940 | "node_modules/postcss": { 941 | "version": "8.5.3", 942 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", 943 | "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", 944 | "funding": [ 945 | { 946 | "type": "opencollective", 947 | "url": "https://opencollective.com/postcss/" 948 | }, 949 | { 950 | "type": "tidelift", 951 | "url": "https://tidelift.com/funding/github/npm/postcss" 952 | }, 953 | { 954 | "type": "github", 955 | "url": "https://github.com/sponsors/ai" 956 | } 957 | ], 958 | "dependencies": { 959 | "nanoid": "^3.3.8", 960 | "picocolors": "^1.1.1", 961 | "source-map-js": "^1.2.1" 962 | }, 963 | "engines": { 964 | "node": "^10 || ^12 || >=14" 965 | } 966 | }, 967 | "node_modules/rollup": { 968 | "version": "4.39.0", 969 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", 970 | "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", 971 | "dev": true, 972 | "dependencies": { 973 | "@types/estree": "1.0.7" 974 | }, 975 | "bin": { 976 | "rollup": "dist/bin/rollup" 977 | }, 978 | "engines": { 979 | "node": ">=18.0.0", 980 | "npm": ">=8.0.0" 981 | }, 982 | "optionalDependencies": { 983 | "@rollup/rollup-android-arm-eabi": "4.39.0", 984 | "@rollup/rollup-android-arm64": "4.39.0", 985 | "@rollup/rollup-darwin-arm64": "4.39.0", 986 | "@rollup/rollup-darwin-x64": "4.39.0", 987 | "@rollup/rollup-freebsd-arm64": "4.39.0", 988 | "@rollup/rollup-freebsd-x64": "4.39.0", 989 | "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", 990 | "@rollup/rollup-linux-arm-musleabihf": "4.39.0", 991 | "@rollup/rollup-linux-arm64-gnu": "4.39.0", 992 | "@rollup/rollup-linux-arm64-musl": "4.39.0", 993 | "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", 994 | "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", 995 | "@rollup/rollup-linux-riscv64-gnu": "4.39.0", 996 | "@rollup/rollup-linux-riscv64-musl": "4.39.0", 997 | "@rollup/rollup-linux-s390x-gnu": "4.39.0", 998 | "@rollup/rollup-linux-x64-gnu": "4.39.0", 999 | "@rollup/rollup-linux-x64-musl": "4.39.0", 1000 | "@rollup/rollup-win32-arm64-msvc": "4.39.0", 1001 | "@rollup/rollup-win32-ia32-msvc": "4.39.0", 1002 | "@rollup/rollup-win32-x64-msvc": "4.39.0", 1003 | "fsevents": "~2.3.2" 1004 | } 1005 | }, 1006 | "node_modules/source-map-js": { 1007 | "version": "1.2.1", 1008 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1009 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1010 | "engines": { 1011 | "node": ">=0.10.0" 1012 | } 1013 | }, 1014 | "node_modules/vite": { 1015 | "version": "6.2.5", 1016 | "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", 1017 | "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", 1018 | "dev": true, 1019 | "dependencies": { 1020 | "esbuild": "^0.25.0", 1021 | "postcss": "^8.5.3", 1022 | "rollup": "^4.30.1" 1023 | }, 1024 | "bin": { 1025 | "vite": "bin/vite.js" 1026 | }, 1027 | "engines": { 1028 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1029 | }, 1030 | "funding": { 1031 | "url": "https://github.com/vitejs/vite?sponsor=1" 1032 | }, 1033 | "optionalDependencies": { 1034 | "fsevents": "~2.3.3" 1035 | }, 1036 | "peerDependencies": { 1037 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 1038 | "jiti": ">=1.21.0", 1039 | "less": "*", 1040 | "lightningcss": "^1.21.0", 1041 | "sass": "*", 1042 | "sass-embedded": "*", 1043 | "stylus": "*", 1044 | "sugarss": "*", 1045 | "terser": "^5.16.0", 1046 | "tsx": "^4.8.1", 1047 | "yaml": "^2.4.2" 1048 | }, 1049 | "peerDependenciesMeta": { 1050 | "@types/node": { 1051 | "optional": true 1052 | }, 1053 | "jiti": { 1054 | "optional": true 1055 | }, 1056 | "less": { 1057 | "optional": true 1058 | }, 1059 | "lightningcss": { 1060 | "optional": true 1061 | }, 1062 | "sass": { 1063 | "optional": true 1064 | }, 1065 | "sass-embedded": { 1066 | "optional": true 1067 | }, 1068 | "stylus": { 1069 | "optional": true 1070 | }, 1071 | "sugarss": { 1072 | "optional": true 1073 | }, 1074 | "terser": { 1075 | "optional": true 1076 | }, 1077 | "tsx": { 1078 | "optional": true 1079 | }, 1080 | "yaml": { 1081 | "optional": true 1082 | } 1083 | } 1084 | }, 1085 | "node_modules/vue": { 1086 | "version": "3.5.13", 1087 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", 1088 | "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", 1089 | "dependencies": { 1090 | "@vue/compiler-dom": "3.5.13", 1091 | "@vue/compiler-sfc": "3.5.13", 1092 | "@vue/runtime-dom": "3.5.13", 1093 | "@vue/server-renderer": "3.5.13", 1094 | "@vue/shared": "3.5.13" 1095 | }, 1096 | "peerDependencies": { 1097 | "typescript": "*" 1098 | }, 1099 | "peerDependenciesMeta": { 1100 | "typescript": { 1101 | "optional": true 1102 | } 1103 | } 1104 | } 1105 | } 1106 | } 1107 | --------------------------------------------------------------------------------