initial stuff

This commit is contained in:
Henri
2025-11-21 08:15:07 +02:00
parent a38a367ac5
commit 8c18827dc5
33 changed files with 522 additions and 148 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>lapikud.github.io</title> <title>Lapikud</title>
<script> <script>
(function () { (function () {
var redirect = sessionStorage.redirect; var redirect = sessionStorage.redirect;

30
package-lock.json generated
View File

@@ -7,6 +7,9 @@
"": { "": {
"name": "lapikud.github.io", "name": "lapikud.github.io",
"version": "0.0.0", "version": "0.0.0",
"dependencies": {
"lucide-svelte": "^0.554.0"
},
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^6.2.1", "@sveltejs/vite-plugin-svelte": "^6.2.1",
"svelte": "^5.43.8", "svelte": "^5.43.8",
@@ -51,7 +54,6 @@
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
@@ -62,7 +64,6 @@
"version": "2.3.5", "version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
@@ -73,7 +74,6 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -83,14 +83,12 @@
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31", "version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@@ -379,7 +377,6 @@
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.7.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.7.tgz",
"integrity": "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==", "integrity": "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"acorn": "^8.9.0" "acorn": "^8.9.0"
@@ -440,14 +437,12 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.15.0", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"bin": { "bin": {
@@ -461,7 +456,6 @@
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -471,7 +465,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -481,7 +474,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -529,14 +521,12 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/esrap": { "node_modules/esrap": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.3.tgz", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.3.tgz",
"integrity": "sha512-T/Dhhv/QH+yYmiaLz9SA3PW+YyenlnRKDNdtlYJrSOBmNsH4nvPux+mTwx7p+wAedlJrGoZtXNI0a0MjQ2QkVg==", "integrity": "sha512-T/Dhhv/QH+yYmiaLz9SA3PW+YyenlnRKDNdtlYJrSOBmNsH4nvPux+mTwx7p+wAedlJrGoZtXNI0a0MjQ2QkVg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15" "@jridgewell/sourcemap-codec": "^1.4.15"
@@ -579,7 +569,6 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/estree": "^1.0.6" "@types/estree": "^1.0.6"
@@ -850,14 +839,21 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lucide-svelte": {
"version": "0.554.0",
"resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.554.0.tgz",
"integrity": "sha512-LLcpHi3SuKup0nVD1kKqo8FDZnjXJp48uST26GGh8Jcyrxqk5gmgpnvKmHsHox674UL3cPS1DCul/wFL7ybGqg==",
"license": "ISC",
"peerDependencies": {
"svelte": "^3 || ^4 || ^5.0.0-next.42"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
@@ -986,7 +982,6 @@
"version": "5.43.13", "version": "5.43.13",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.13.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.13.tgz",
"integrity": "sha512-K1QJbpc71OHs3egN/ZHN+nsH/eE/eJQUbtlTAAXVejOOjsFx9hM96fCX5fwPQIYclJfVS8D96RDFA76nZsrJ3w==", "integrity": "sha512-K1QJbpc71OHs3egN/ZHN+nsH/eE/eJQUbtlTAAXVejOOjsFx9hM96fCX5fwPQIYclJfVS8D96RDFA76nZsrJ3w==",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@@ -1136,7 +1131,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
"dev": true,
"license": "MIT" "license": "MIT"
} }
} }

View File

@@ -15,5 +15,8 @@
}, },
"overrides": { "overrides": {
"vite": "npm:rolldown-vite@7.2.5" "vite": "npm:rolldown-vite@7.2.5"
},
"dependencies": {
"lucide-svelte": "^0.554.0"
} }
} }

28
public/locales/en.json Normal file
View File

@@ -0,0 +1,28 @@
{
"nav": {
"about": "About Us",
"aboutPages": {
"info": "What we're doing?",
"join": "Join Us",
"mentors": "Mentors",
"board": "Board"
},
"events": "Events",
"eventsPages": {
"calendar": "Events Calendar",
"workshops": "Workshops",
"fresh": "Freshmen Initiation",
"repair": "Repair Café"
},
"contact": "Contact",
"helpdesk": "Helpdesk"
},
"content": {},
"footer": {
"address": "Address: ",
"mail": "E-mail: ",
"messenger": "Messenger: ",
"reg": "Reg. code: ",
"toast": "Our socials! Follow us for updates and news."
}
}

28
public/locales/est.json Normal file
View File

@@ -0,0 +1,28 @@
{
"nav": {
"about": "Meist",
"aboutPages": {
"info": "Mis Teeme?",
"join": "Liitu Meiega",
"mentors": "Mentorid",
"board": "Juhatus"
},
"events": "Üritused",
"eventsPages": {
"calendar": "Ürituste Kalender",
"workshops": "Koolitused",
"fresh": "Rebaste Ristimine",
"repair": "Remondikohvik"
},
"contact": "Kontakt",
"helpdesk": "Helpdesk"
},
"content": {},
"footer": {
"address": "Aadress: ",
"mail": "E-post: ",
"messenger": "Messenger: ",
"reg": "Reg. kood: ",
"toast": "Meie sotsiaalid! Jälgi meid uuenduste ja uudiste saamiseks."
}
}

View File

@@ -1,20 +1,12 @@
<script> <script>
import Router from "./lib/Router.svelte"; import { Router, text } from "$lib";
import { routes } from "./routes/index.js"; import { routes } from "./routes/index.js";
import { theme, toggle } from "./lib/theme.js";
import Navbar from "./layout/Navbar.svelte"; import Navbar from "./layout/Navbar.svelte";
import Footer from "./layout/Footer.svelte"; import Footer from "./layout/Footer.svelte";
</script> </script>
<Navbar /> {#if $text.nav}
<Router {routes} /> <!--<Navbar /> Ära vaata seda faili!! -->
<Footer /> <Router {routes} />
<Footer />
<!-- use $theme to show current state --> {/if}
<button
onclick={toggle}
style="position:fixed;right:1rem;bottom:1rem"
aria-label="Toggle theme"
>
{#if $theme === "dark"}☀️{:else}🌙{/if}
</button>

View File

@@ -12,12 +12,37 @@ Running `npm run dev` starts a local development server with hot reloading (so y
> >
> DO NOT TOUCH unless you know what you're doing! If you want to tinker, then do. When it breaks it just doesn't load any pages > DO NOT TOUCH unless you know what you're doing! If you want to tinker, then do. When it breaks it just doesn't load any pages
## Internationalization (i18n)
This project uses a simple custom i18n solution defined in `src/lib/i18n.js`.
Import it with `$lib` alias as:
```js
import { currentLang, text, switchLang } from "$lib";
```
text contains a Svelte store that holds the current translations. To use it in a Svelte component, use the `$` prefix to auto-subscribe to the store:
```js
<p>{$text.key}</p> //replace the key with the translation key example: "nav.about"
```
## Styling ## Styling
Look at `src/app.css` for global styles and CSS variables. Each Svelte component can also have its own styles scoped to that component Look at `src/app.css` for global styles and CSS variables. Each Svelte component can also have its own styles scoped to that component
Variables are defined in `:root` Variables are defined in `:root`
we only have 3 colors in out palette + 2 variants:
- `--orange`
- `--black`
- `--white`
- `--off-white`
- `--off-black`
Add new ones only if they seem universal enough to be used in multiple places, don't add stuff like `--space-100: -888px` noone else will ever use that, if you really need it just add it to the component's own style block Add new ones only if they seem universal enough to be used in multiple places, don't add stuff like `--space-100: -888px` noone else will ever use that, if you really need it just add it to the component's own style block
## Layout ## Layout
@@ -39,3 +64,14 @@ There are also helper classes for responsive design:
These classes use media queries defined in `src/app.css` based on common breakpoints. These classes use media queries defined in `src/app.css` based on common breakpoints.
also things like `.stack`, `.inline`, `.grid`. Go look at `src/app.css` for what they do. also things like `.stack`, `.inline`, `.grid`. Go look at `src/app.css` for what they do.
## Routing
Routing is handled by the custom router in `src/lib/Router.svelte` and `src/lib/router/router.js`.
To add new routes, edit `src/routes/index.js` and add your new route there.
Import router functions as:
```js
import { navigate, goBack, reload, getPath, getQuery } from "$lib"; // only import what you need
```

View File

@@ -3,14 +3,11 @@
/* Design tokens & theme variables (light defaults) */ /* Design tokens & theme variables (light defaults) */
:root { :root {
/* color tokens (light defaults) */ /* color tokens (light defaults) */
--color-bg: #ffffff; --orange: #f0941d;
--color-surface: #f7f8fb; --black: #000000;
--color-text: #0f1720; --off-black: #0d0d0d;
--color-muted: #6b7280; --white: #ffffff;
--color-primary: #4f46e5; --off-white: #fffdfa;
--color-primary-600: #4338ca;
--color-border: rgba(15, 23, 32, 0.08);
--color-danger: #ef4444;
/* layout tokens */ /* layout tokens */
--site-padding: clamp(1rem, 2vw, 2rem); /* horizontal page padding */ --site-padding: clamp(1rem, 2vw, 2rem); /* horizontal page padding */
@@ -31,7 +28,8 @@
--radius-sm: 6px; --radius-sm: 6px;
--radius-md: 10px; --radius-md: 10px;
--radius-lg: 14px; --radius-lg: 14px;
--shadow-1: 0 1px 2px rgba(2, 6, 23, 0.06), 0 1px 3px rgba(2, 6, 23, 0.04); --shadow-1: 0 1px 2px rgba(240, 148, 29, 0.2),
0 1px 3px rgba(240, 148, 29, 0.12);
/* breakpoints */ /* breakpoints */
--bp-sm: 480px; --bp-sm: 480px;
@@ -59,31 +57,6 @@
--accent: var(--color-primary); --accent: var(--color-primary);
} }
/* Dark theme overrides: set document.documentElement.dataset.theme = "dark" */
[data-theme="dark"] {
--color-bg: #0b1220;
--color-surface: #0f1724;
--color-text: #e6eef8;
--color-muted: #9aa5b2;
--color-primary: #7c6cff;
--color-primary-600: #6d5cff;
--color-border: rgba(255, 255, 255, 0.06);
--button-bg: #0f1724;
--card-bg: linear-gradient(
180deg,
rgba(18, 20, 28, 0.9),
rgba(15, 17, 24, 0.85)
);
}
/* let the browser apply native form rendering for color-scheme */
:root {
color-scheme: light;
}
[data-theme="dark"] {
color-scheme: dark;
}
/* Global box sizing & accessible defaults */ /* Global box sizing & accessible defaults */
*, *,
*::before, *::before,

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,20 +1,27 @@
<script> <script>
export let onClick = () => {} export let onClick = () => {};
</script> </script>
<button on:click={onClick} class="btn"> <button onclick={onClick} class="btn">
<slot /> <slot />
</button> </button>
<style> <style>
.btn { .btn {
background: var(--button-bg); background: var(--white);
color: var(--button-text); color: var(--black);
border-radius: 8px; border-radius: 8px;
border: 1px solid transparent; border: 1.5px solid transparent;
border-color: var(--orange);
padding: 0.6em 1.2em; padding: 0.6em 1.2em;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5em;
}
.btn:hover {
background: var(--orange);
} }
.btn:hover { border-color: var(--primary); }
</style> </style>

View File

@@ -0,0 +1,26 @@
<script>
export let onClick = () => {};
</script>
<button onclick={onClick} class="btn">
<slot />
</button>
<style>
.btn {
background: var(--white);
color: var(--black);
border-radius: 8px;
border: 1.5px solid transparent;
padding: 0.6em 1.2em;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5em;
}
.btn:hover {
background: var(--orange);
}
</style>

View File

@@ -1,8 +0,0 @@
<script>
export let className = '';
export let as = 'div';
</script>
<svelte:component this={as} class={`card ${className}`}>
<slot />
</svelte:component>

View File

@@ -1,9 +0,0 @@
<script>
export let fluid = false; // when true, removes max-width
export let className = '';
export let as = 'div';
</script>
<svelte:component this={as} class={`container ${fluid ? 'container--fluid' : ''} ${className}`}>
<slot />
</svelte:component>

View File

@@ -0,0 +1,71 @@
<script>
import Button from "./Button.svelte";
import ButtonBorderless from "./ButtonBorderless.svelte";
// Props
export let name = "";
export let variant = "default"; // 'default' | 'borderless' aka 'Button' | 'ButtonBorderless'
let isOpen = false;
const toggle = () => (isOpen = !isOpen);
const close = () => (isOpen = false);
function handleFocusOut({ relatedTarget, currentTarget }) {
if (
relatedTarget instanceof HTMLElement &&
currentTarget.contains(relatedTarget)
)
return;
close();
}
</script>
<div class="relative inline-block" on:focusout={handleFocusOut}>
{#if variant === "borderless"}
<ButtonBorderless onClick={toggle}>
{name}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-5 ml-2 mt-1 transition-transform inline-block"
style:transform={isOpen ? "rotate(180deg)" : "none"}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m19.5 8.25-7.5 7.5-7.5-7.5"
/>
</svg>
</ButtonBorderless>
{:else}
<Button onClick={toggle}>
{name}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-5 ml-2 mt-1 transition-transform inline-block"
style:transform={isOpen ? "rotate(180deg)" : "none"}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m19.5 8.25-7.5 7.5-7.5-7.5"
/>
</svg>
</Button>
{/if}
<div
class="absolute mt-2 rounded-md shadow-lg bg-white ring-1 ring-black/5 overflow-hidden flex flex-col"
style:visibility={isOpen ? "visible" : "hidden"}
>
<slot />
</div>
</div>

View File

@@ -1,10 +0,0 @@
<script>
export let min = '220px'; // minimum column width
export let gap = 'var(--space-3)';
export let className = '';
export let as = 'div';
</script>
<svelte:component this={as} class={`grid ${className}`} style={`grid-template-columns: repeat(auto-fit, minmax(${min}, 1fr)); gap: ${gap};`}>
<slot />
</svelte:component>

View File

@@ -1,11 +0,0 @@
<script>
// Vertical stack helper. Accepts a gap and alignment so pages can tune spacing.
export let gap = 'var(--space-3)';
export let align = 'stretch';
export let className = '';
export let as = 'div';
</script>
<svelte:component this={as} class={`stack ${className}`} style={`gap: ${gap}; align-items: ${align};`}>
<slot />
</svelte:component>

View File

@@ -1,5 +1,8 @@
export { default as Container } from './Container.svelte' export { default as Container } from './layout/Container.svelte'
export { default as Card } from './Card.svelte' export { default as Card } from './layout/Card.svelte'
export { default as Stack } from './Stack.svelte' export { default as Stack } from './layout/Stack.svelte'
export { default as Grid } from './Grid.svelte' export { default as Grid } from './layout/Grid.svelte'
export { default as Button } from './Button.svelte' export { default as Button } from './Button.svelte'
export { default as ButtonBorderless } from './ButtonBorderless.svelte'
export { default as Dropdown } from './Dropdown.svelte'

View File

@@ -0,0 +1,12 @@
<!--
A simple presentational container that renders a `div` with the `card` class.
-->
<script>
export let className = "";
export let background = "var(--off-white)";
</script>
<div class={`card ${className}`} style="background: {background}">
<slot />
</div>

View File

@@ -0,0 +1,15 @@
<!--
A responsive container that centers content and optionally
removes the max-width when `fluid` is true. Use `className` to
add additional CSS classes to the root element. Content is
projected via the default slot.
-->
<script>
export let fluid = false; // true removes max-width
export let className = "";
</script>
<div class={`container ${fluid ? "container--fluid" : ""} ${className}`}>
<slot />
</div>

View File

@@ -0,0 +1,18 @@
<!--
A responsive grid wrapper using CSS Grid. It exposes `min` and
`gap` props to control the responsive column minimum width and
spacing. Content is projected into the default slot.
-->
<script>
export let min = "220px"; // minimum column width
export let gap = "var(--space-3)";
export let className = "";
</script>
<div
class={`grid ${className}`}
style={`grid-template-columns: repeat(auto-fit, minmax(${min}, 1fr)); gap: ${gap};`}
>
<slot />
</div>

View File

@@ -0,0 +1,13 @@
<!--
A simple stack/column layout that stacks children vertically.
-->
<script>
export let gap = "var(--space-3)";
export let align = "stretch";
export let className = "";
</script>
<div class={`stack ${className}`} style={`gap: ${gap}; align-items: ${align};`}>
<slot />
</div>

16
src/layout/Center.svelte Normal file
View File

@@ -0,0 +1,16 @@
<script>
export let dir = "row"; // 'row' | 'col' (default: 'row')
export let gapClass = "gap-4";
export let className = "";
</script>
<div
class={"flex " +
(dir === "row" ? "flex-row" : "flex-col") +
" items-center justify-center " +
gapClass +
" " +
className}
>
<slot />
</div>

View File

@@ -0,0 +1,65 @@
<script>
import {
Button,
ButtonBorderless,
Dropdown,
Grid,
} from "@/components/index.js";
import Center from "./Center.svelte";
import { navigate, getPath, currentLang, text, switchLang } from "$lib";
// Icon imports (Lucide)
import Coffee from "lucide-svelte/icons/coffee";
import CalendarClock from "lucide-svelte/icons/calendar-clock";
import Presentation from "lucide-svelte/icons/presentation";
import Swords from "lucide-svelte/icons/swords";
import Lectern from "lucide-svelte/icons/lectern";
import Bot from "lucide-svelte/icons/bot";
import HandHeart from "lucide-svelte/icons/hand-heart";
import Trophy from "lucide-svelte/icons/trophy";
</script>
<Grid>
<nav class="w-full h-16">
<Center gapClass="gap-4">
<Button onClick={() => navigate("/")}>Home</Button>
<ButtonBorderless onClick={() => navigate("/about")}
>About</ButtonBorderless
>
<Dropdown name={$text.nav.about} variant="borderless">
<ButtonBorderless onClick={() => navigate("/mis-teeme")}
>{$text.nav.aboutPages.info}</ButtonBorderless
>
<ButtonBorderless onClick={() => navigate("/liitu-meiega")}
><Bot />{$text.nav.aboutPages.join}</ButtonBorderless
>
<ButtonBorderless onClick={() => navigate("/mentorid")}
><HandHeart />{$text.nav.aboutPages.mentors}</ButtonBorderless
>
<ButtonBorderless onClick={() => navigate("/juhatus")}
><Lectern />{$text.nav.aboutPages.board}</ButtonBorderless
>
</Dropdown>
<Dropdown name={$text.nav.events} variant="borderless">
<ButtonBorderless onClick={() => navigate("/kalender")}
><CalendarClock />{$text.nav.eventsPages.calendar}</ButtonBorderless
>
<ButtonBorderless
onClick={() => navigate("https://asikarikas.ee/", { external: true })}
><Trophy />ASI Karikas</ButtonBorderless
>
<ButtonBorderless onClick={() => navigate("/mentorid")}
><Presentation />{$text.nav.eventsPages.workshops}</ButtonBorderless
>
<ButtonBorderless onClick={() => navigate("/rebased")}
><Swords />{$text.nav.eventsPages.fresh}</ButtonBorderless
>
<ButtonBorderless
onClick={() =>
navigate("https://remondikohvik.lapikud.ee/", { external: true })}
><Coffee />{$text.nav.eventsPages.repair}</ButtonBorderless
>
</Dropdown>
</Center>
</nav>
</Grid>

35
src/lib/i18n.js Normal file
View File

@@ -0,0 +1,35 @@
import { writable } from 'svelte/store';
// Load saved language or default to 'est'
const savedLang = typeof window !== 'undefined'
? localStorage.getItem('language') || 'est'
: 'est';
// Store for current language
export const currentLang = writable(savedLang);
// Store for translations
export const text = writable({});
// Load translations for a specific language
async function loadTranslations(lang) {
try {
const response = await fetch(`/locales/${lang}.json`);
const data = await response.json();
text.set(data);
} catch (error) {
console.error(`Failed to load translations for ${lang}:`, error);
}
}
// Initialize translations
loadTranslations(savedLang);
// Function to switch language
export function switchLang(lang) {
currentLang.set(lang);
loadTranslations(lang);
if (typeof window !== 'undefined') {
localStorage.setItem('language', lang);
}
}

4
src/lib/index.js Normal file
View File

@@ -0,0 +1,4 @@
export { navigate, goBack, reload, getPath, getQuery } from './router/router.js';
export { default as Router } from './router/Router.svelte';
export { currentLang, text, switchLang } from './i18n.js';

View File

@@ -2,6 +2,11 @@
<!-- unless the router is broken 👉👈 --> <!-- unless the router is broken 👉👈 -->
<script> <script>
export function routeTo(path) {
window.history.pushState({}, "", path);
window.dispatchEvent(new PopStateEvent("popstate"));
}
let { routes } = $props(); let { routes } = $props();
let currentPath = $state(window.location.pathname); let currentPath = $state(window.location.pathname);

44
src/lib/router/router.js Normal file
View File

@@ -0,0 +1,44 @@
/**
* Navigate to a path (internal or external)
* @param {string} path - The path or URL to navigate to
* @param {boolean} external - Whether this is an external link (default: false)
*/
export function navigate(path, external = false) {
if (external) {
window.location.href = path;
} else {
window.history.pushState({}, "", path);
window.dispatchEvent(new PopStateEvent('popstate'));
}
}
/**
* Go back in browser history
*/
export function goBack() {
window.history.back();
}
/**
* Reload the current page
*/
export function reload() {
window.location.reload();
}
/**
* Get the current pathname
* @returns {string} Current path
*/
export function getPath() {
return window.location.pathname;
}
/**
* Get URL query parameters as an object
* @returns {Object} Query parameters
*/
export function getQuery() {
const params = new URLSearchParams(window.location.search);
return Object.fromEntries(params.entries());
}

View File

@@ -1,22 +0,0 @@
// THEME STORE for LIGHT / DARK mode
import { writable } from 'svelte/store'
const init = (() => {
try {
const stored = localStorage.getItem('theme')
if (stored === 'light' || stored === 'dark') return stored
} catch (e) { }
return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light'
})()
export const theme = writable(init)
theme.subscribe(value => {
try { localStorage.setItem('theme', value) } catch (e) { }
document.documentElement.dataset.theme = value
})
export function toggle() {
theme.update(t => t === 'dark' ? 'light' : 'dark')
}

View File

@@ -1,2 +0,0 @@
<h1>Demo Page</h1>
<a href="/">Back to Home</a>

View File

@@ -1,5 +1,47 @@
<h1>Home</h1> <script>
<nav> import {
<a href="/demo">Demo</a> Button,
<a href="/contact">Contact</a> ButtonBorderless,
</nav> Container,
Card,
Stack,
Grid,
} from "@/components/index.js";
</script>
<Container>
<Stack gap="var(--space-6)">
<Card>
<h1>Kes me oleme?</h1>
<p>
Organisatsioon loodi 2002. aastal informaatika tudengite poolt, kelle
erialatunnuseks oli “LAP”. Targad mehed otsustasid, et järelikult on nad
“Lapikud”.
</p>
<Button href="/docs/getting-started">Get Started</Button>
<ButtonBorderless href="/docs/components"
>Explore Components</ButtonBorderless
>
</Card>
<Grid min="250px" gap="var(--space-4)">
<Card>
<h2>Mis on HELPDESK?</h2>
<p>
HELPDESK on MTÜ Lapikute poolt pakutav arvutiabiteenus. Teenus on
suunatud tudengitele, õppejõududele ning kõikidele huvilistele ka
väljaspool Tallinna Tehnikaülikooli. Meie meeskond koosneb kogenud ja
nutikatest Tallinna Tehnikaülikooli IT-tudengitest.
</p>
</Card>
<Card>
<h2>Mis teenuseid osutame?</h2>
<p>
Arvuti riistvara diagnostika ja remont Arvuti riistvara hooldus
Tarkvara diagnostika ja seadistamine Tarkvara paigaldamine Muu
elektroonika parandamine
</p>
</Card>
</Grid>
</Stack>
</Container>

1
src/routes/Test.svelte Normal file
View File

@@ -0,0 +1 @@
<div>testing</div>

View File

@@ -1,10 +1,10 @@
// ADD PAGES HERE TO REGISTER ROUTES // ADD PAGES HERE TO REGISTER ROUTES
// examples: '/page', '/page/:id' // examples: '/page', '/page/:id'
import Demo from './Demo.svelte'
import Home from './Home.svelte' import Home from './Home.svelte'
import Test from './Test.svelte'
export const routes = { export const routes = {
'/': Home, '/': Home,
'/demo': Demo, '/juhatus': Test,
} }

View File

@@ -8,4 +8,10 @@ export default defineConfig({
server: { server: {
historyApiFallback: true, historyApiFallback: true,
}, },
resolve: {
alias: {
'@': '/src',
'$lib': '/src/lib'
}
}
}) })