components and tailwind rework

This commit is contained in:
Henri
2026-03-05 16:43:56 +02:00
parent 4571d22644
commit 1b704c7cc4
19 changed files with 257 additions and 373 deletions

View File

@@ -1,13 +1,11 @@
/** @type { import('@storybook/svelte-vite').StorybookConfig } */
const config = {
"stories": [
"../src/**/*.stories.@(js|ts|svelte)"
],
"addons": [
"@storybook/addon-svelte-csf"
],
"framework": "@storybook/svelte-vite"
stories: ['../src/**/*.stories.svelte'],
addons: ['@storybook/addon-svelte-csf'],
framework: {
name: '@storybook/svelte-vite',
options: {},
},
};
export default config;

37
generate-stories.js Normal file
View File

@@ -0,0 +1,37 @@
import { readFileSync, writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const svelteSrc = readFileSync(resolve(__dirname, './src/components/Svg.svelte'), 'utf-8');
const match = svelteSrc.match(/export const VARIANTS\s*=\s*\[([^\]]+)\]/);
if (!match) {
console.error('Could not find VARIANTS in Svg.svelte');
process.exit(1);
}
const variants = match[1]
.split(',')
.map(v => v.trim().replace(/['"]/g, ''))
.filter(Boolean);
const stories = variants.map(v => `<Story name="${v}" args={{ type: '${v}' }} />`).join('\n');
const output = `<script context="module">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Svg from '../components/Svg.svelte';
const { Story } = defineMeta({
title: 'Components/Svg',
component: Svg,
});
</script>
${stories}
`;
const outPath = resolve(__dirname, './src/stories/Svg.stories.svelte');
writeFileSync(outPath, output, 'utf-8');
console.log(`✅ Generated ${variants.length} stories: ${variants.join(', ')}`);

View File

@@ -8,16 +8,18 @@
"build": "vite build",
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"prestorybook": "node generate-stories.js",
"prebuild-storybook": "node generate-stories.js"
},
"devDependencies": {
"@storybook/addon-svelte-csf": "^5.0.11",
"@storybook/svelte-vite": "^10.2.15",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/js-yaml": "^4.0.9",
"svelte": "^5.43.8",
"vite": "npm:rolldown-vite@7.2.5",
"storybook": "^10.2.15",
"@storybook/svelte-vite": "^10.2.15",
"@storybook/addon-svelte-csf": "^5.0.11"
"svelte": "^5.43.8",
"vite": "npm:rolldown-vite@7.2.5"
},
"overrides": {
"vite": "npm:rolldown-vite@7.2.5"

View File

@@ -23,11 +23,6 @@
url: https://taltech.ee/et/infotehnoloogia-teaduskond
additionalText:
- name: HexTech Solutions
image: hextech_logo.svg
url: https://hextech.ee/
additionalText:
- name: EAS & MKM
image: eas.svg
url: https://eis.ee/

View File

@@ -2,54 +2,42 @@
@import "tailwindcss";
/* Overwriting tailwindcss specific variables */
@theme {
--color-orange-500: #f0941d;
--color-orange-300: #ffa940;
--color-orange-600: #d67d0a;
--color-gray-100: #fffdfa;
--color-gray-900: #0d0d0d;
--spacing-0: 0px;
--spacing-1: 4px;
--spacing-2: 8px;
--spacing-3: 16px;
--spacing-4: 24px;
--spacing-5: 32px;
--spacing-6: 48px;
--max-width-2xl: 700px;
--max-width-3xl: 800px;
--max-width-4xl: 900px;
}
/* Design tokens & theme variables (light defaults) */
:root {
/* color tokens (light defaults) */
--orange: #f0941d;
--orange-light: #ffa940;
--orange-dark: #d67d0a;
--black: #000000;
--off-black: #0d0d0d;
--white: #ffffff;
--off-white: #fffdfa;
--gray-50: #fafafa;
--gray-100: #f5f5f5;
--gray-200: #e5e5e5;
--gray-300: #d4d4d4;
--gray-800: #262626;
--gray-900: #171717;
/* layout tokens */
--site-padding: clamp(1rem, 2vw, 2rem); /* horizontal page padding */
--content-max: 1200px; /* main content max width */
--nav-height: 64px;
--footer-height: 80px;
/* spacing scale */
--space-0: 0px;
--space-1: 4px;
--space-2: 8px;
--space-3: 16px;
--space-4: 24px;
--space-5: 32px;
--space-6: 48px;
/* radius / shadows */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-xl: 20px;
--shadow-1: 0 1px 2px rgba(240, 148, 29, 0.2),
0 1px 3px rgba(240, 148, 29, 0.12);
--shadow-2: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-3: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
--shadow-glow: 0 0 20px rgba(240, 148, 29, 0.3);
/* breakpoints */
--bp-sm: 480px;
--bp-md: 768px;
--bp-lg: 1024px;
--bp-xl: 1280px;
--spacing-0: 0px;
--spacing-1: 4px;
--spacing-2: 8px;
--spacing-3: 16px;
--spacing-4: 24px;
--spacing-5: 32px;
--spacing-6: 48px;
/* type scale (fluid base + modular sizes) */
--fs-base: clamp(0.95rem, 0.9vw + 0.9rem, 1rem);
@@ -59,16 +47,6 @@
--fs-lg: 1.125rem;
--fs-xl: 1.5rem;
--line-height: 1.45;
/* derived component tokens that components can rely on */
--button-bg: var(--color-surface);
--button-text: var(--color-text);
--card-bg: linear-gradient(
180deg,
rgba(255, 255, 255, 0.98),
rgba(255, 255, 255, 0.96)
);
--accent: var(--color-primary);
}
/* Global box sizing & accessible defaults */
@@ -88,8 +66,6 @@ html {
}
body {
margin: 0;
color: var(--color-text);
background: var(--color-bg);
line-height: var(--line-height);
min-height: 100vh;
-webkit-tap-highlight-color: transparent;
@@ -100,22 +76,18 @@ body {
/* Basic link & button defaults that components inherit */
a {
color: var(--color-primary);
text-decoration: none;
font-weight: 550;
}
a:hover {
text-decoration: underline;
color: var(--color-primary-600);
}
button,
input[type="button"],
input[type="submit"] {
font: inherit;
color: var(--button-text);
background: var(--button-bg);
border: 1px solid var(--color-border);
border: 1px solid var(--color-border); /* TODO: fix css magic (we need the border, but the variable doesnt exist, but you cant remove it either) */
padding: 0.5em 0.9em;
border-radius: var(--radius-sm);
cursor: pointer;
@@ -126,17 +98,10 @@ button:active {
transform: translateY(1px);
}
button:focus {
outline: 3px solid color-mix(in srgb, var(--color-primary) 14%, transparent);
outline: 3px solid color-mix(in srgb, var(--color-primary) 14%, transparent); /* TODO: change same magic here */
outline-offset: 2px;
}
/* Simple responsive grid that adapts to available width */
.grid {
display: grid;
gap: var(--space-3);
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
/* Utility: visually hidden (accessible labels) */
.sr-only,
.visually-hidden {
@@ -167,49 +132,34 @@ button:focus {
}
}
/* Navbar & footer helpers so components can assume consistent heights */
.site-header {
height: var(--nav-height);
display: flex;
align-items: center;
background: transparent;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 40;
border-bottom: 1px solid var(--color-border);
backdrop-filter: blur(6px);
}
.site-footer {
height: var(--footer-height);
display: flex;
align-items: center;
border-top: 1px solid var(--color-border);
background: transparent;
}
/* Small form controls default */
.input {
padding: 0.6em 0.8em;
border-radius: var(--radius-sm);
border: 1px solid var(--color-border);
background: transparent;
color: inherit;
}
/* Utility to visually reduce emphasis (muted text) */
.muted {
color: var(--color-muted);
font-size: 0.95em;
}
/* Accessibility: respect reduced motion */
/* Accessibility settings */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
animation: none !important;
transition: none !important;
scroll-behavior: auto !important;
}
/* Automatically apply final animation states */
[data-animation] { /* Everything with animation needs to be marked like this: <div class="fade-in" data-animation>Hello World</div> */
all: unset !important;
}
}
@media (prefers-reduced-transparency: reduce) {
* {
background: solid !important;
backdrop-filter: none !important;
}
}
@media (forced-colors: active) {
* {
background: Window !important;
color: WindowText !important;
border-color: WindowText !important;
}
a {
color: Highlight !important;
}
}

View File

@@ -1,23 +1,10 @@
<script>
export let onClick = () => {};
export let buttonHoverStyle = "";
export let style = "";
let isHovered = false;
</script>
<button
on:click={onClick}
on:mouseenter={() => (isHovered = true)}
on:mouseleave={() => (isHovered = false)}
class={`btn border-[1.5px] px-5 py-2.5 font-medium cursor-pointer flex items-center gap-2 whitespace-nowrap ${$$restProps.class}`}
style={isHovered && buttonHoverStyle ? buttonHoverStyle : style}
class="btn border-[1.5px] px-5 py-2.5 font-medium cursor-pointer flex items-center gap-2 whitespace-nowrap {$$props.class ?? ''}"
>
<slot />
</button>
<style>
.btn {
transition: background-color 0.2s ease, color 0.2s ease;
}
</style>

View File

@@ -4,7 +4,6 @@
export let name = "";
export let buttonClass = "";
export let panelClassName = "";
export let buttonHoverStyle = "";
let isOpen = false;
@@ -22,7 +21,7 @@
</script>
<div class="relative inline-block" on:focusout={handleFocusOut}>
<Button onClick={toggle} class={buttonClass} {buttonHoverStyle}>
<Button onClick={toggle} class={buttonClass}>
{name}
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -11,9 +11,16 @@ under components are building blocks that are not layout related.
- Button: a simple button component with some styling.
- Dropdown: a dropdown component that can be used to show/hide content.
- Slideshow: a simple slideshow component that can be used to show multiple images in a slideshow format. Includes options to enable/disable autoplay and to set the autoplay interval.
- Svg: a component that renders different SVG icons based on the `type` prop. See the comment after the `type` prop in `Svg.svelte` for the available types.
- Image: a component that renders an image with some styling. It accepts a lot of props. Main ones are `src` (the main image source that is used if anything else fails or not provided), `alt` (the alt text for the image), `srcSet` (a string of comma-separated image sources for different screen sizes, jpg or png files), `webpSrc` (the webp image source as a webp file), `webpSrcSet` (a string of comma-separated webp image sources for different screen sizes). We use 2 webp images: 400 and 800 width, and one jpg image 800 width as default src for fallback.
- Svg: a component that renders different SVG icons based on the `type` prop. See the VARIANTS constant in `Svg.svelte` or storybook for the available types.
- Image (Use for larger images): a component that renders an image with some styling. It accepts a lot of props. Main ones are `src` (the main image source that is used if anything else fails or not provided), `alt` (the alt text for the image), `srcSet` (a string of comma-separated image sources for different screen sizes, jpg or png files), `webpSrc` (the webp image source as a webp file), `webpSrcSet` (a string of comma-separated webp image sources for different screen sizes). We use 2 webp images: 400 and 800 width, and one jpg image 800 width as default src for fallback.
# Layout components
under components/layout are components that are used to structure the page, such as Container, Stack, Center, etc.
under components/layout are components that are used to structure the page, such as Container, Stack, Center, etc.
- Container: a simple container component that centers its content and adds some padding.
- Stack: a component that stacks its children vertically or horizontally.
- Center: a component that centers its content both horizontally and vertically.
- Section: a component that is used to create sections on the page.
- Card: a component that adds some padding, a border, and a shadow to its content, used to create cards on the page.
- Grid: a component that creates a CSS grid layout for its children, with customizable columns and gap. It is dynamic and changes automatically with page size, best practice is to set `columns` for desktop and `mobileColumns` for mobile views to control the behavior if needed.

View File

@@ -1,5 +1,9 @@
<script context="module">
export const VARIANTS = ['instagram','facebook','github','connector','mirrorV','mirrorH','mirrorVH','branch','branchH'];
</script>
<script>
export let type; // "connector", "mirrorV", "mirrorH", "mirrorVH", "branch"
export let type;
export let className = "";
</script>
@@ -49,6 +53,23 @@
</g>
</svg>
{:else if type === "instagram"}
<svg class="w-8 h-8 {className}" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
</svg>
{:else if type === "facebook"}
<svg class="w-8 h-8 {className}" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
{:else if type === "github"}
<svg class="w-8 h-8 {className}" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
{:else}
<div>Unknown type: {type}</div>
{/if}

View File

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

View File

@@ -1,35 +1,41 @@
<!--
A responsive grid wrapper using CSS Grid. It exposes `min`, `columns`,
and `gap` props to control the responsive column minimum width, fixed
column count, and spacing. Content is projected into the default slot.
-->
<script>
export let min = "220px"; // minimum column width (used when columns is not set)
export let columns = null; // fixed number of columns (e.g., "2", "3", "4")
export let gap = "var(--space-3)";
export let min = "220px"; // min column width (used when columns not set)
export let columns = null; // fixed number of columns for desktop
export let mobileColumns = null; // fixed number of columns for mobile
export let gap = "gap-3";
export let className = "";
$: gridTemplateColumns = columns
? `repeat(${columns}, 1fr)`
: '1fr';
$: desktopColumns = columns ? `repeat(${columns}, 1fr)` : '1fr';
$: mobileCols = mobileColumns ? `repeat(${mobileColumns}, 1fr)` : '1fr';
</script>
<div
class="grid {className}"
style="--min: {min}; --gap: {gap}; --columns: {gridTemplateColumns}; grid-template-columns: var(--columns); gap: var(--gap);"
class="grid {className} {gap}"
style="
--min: {min};
--desktop-columns: {desktopColumns};
--mobile-columns: {mobileCols};
grid-template-columns: var(--mobile-columns);
"
>
<slot />
</div>
<style>
/* Mobile first: use mobileColumns by default */
.grid {
grid-template-columns: var(--mobile-columns);
}
/* Desktop / tablet screens */
@media (min-width: 768px) {
.grid {
grid-template-columns: var(--columns) !important;
grid-template-columns: var(--desktop-columns) !important;
}
.grid:not([style*="--columns: repeat"]) {
/* fallback for when columns are not set, use minmax */
.grid:not([style*="--desktop-columns: repeat"]) {
grid-template-columns: repeat(auto-fit, minmax(var(--min), 1fr)) !important;
}
}
</style>
</style>

View File

@@ -1,6 +1,6 @@
<script>
export let background = "transparent"; // CSS color code or color variable name from app.css
export let padding = "normal"; // 'none' | 'small' | 'normal' | 'large'
export let bg = "";
export let padding = "normal";
export let fullWidth = false;
export let id = "";
export let className = "";
@@ -12,25 +12,13 @@
normal: "py-12 max-md:py-8",
large: "py-[clamp(3rem,8vw,6rem)] max-md:py-12",
};
// Convert background to appropriate CSS value
$: backgroundStyle = (() => {
if (!background || background === "transparent") return "";
// If it starts with # or rgb/rgba or hsl, it's a direct color
if (background.startsWith("#") || background.startsWith("rgb") || background.startsWith("hsl")) {
return background;
}
// Otherwise treat as CSS variable name
return `var(--${background})`;
})();
</script>
<section
{id}
class="w-full {paddingClasses[padding]} {className}"
style={backgroundStyle ? `background: ${backgroundStyle};` : ""}
class="w-full {paddingClasses[padding]} {bg} {className}"
>
<div class="{fullWidth ? '' : 'max-w-[1200px]'} mx-auto px-(--site-padding)">
<slot />
</div>
</section>
</section>

View File

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

View File

@@ -4,26 +4,27 @@ import {
Section,
Container,
Center,
Svg,
} from "$components";
</script>
<Section background="off-black" className="text-white">
<Section bg="bg-gray-900" className="text-white">
<Grid>
<div>
<h3 class="text-3xl font-light" style="padding-bottom: var(--space-4);">MTÜ Lapikud</h3>
<h3 class="text-3xl font-light pb-4">MTÜ Lapikud</h3>
<Container center={false}>
<address class="not-italic" style="padding-bottom: var(--space-4);">
Aadress: <a href="https://maps.app.goo.gl/1pCDMVNaMD29G7y78" style="color: var(--orange)">Akadeemia tee 5, 12616 Tallinn, Eesti</a>
<address class="not-italic pb-4">
Aadress: <a href="https://maps.app.goo.gl/1pCDMVNaMD29G7y78" class="text-orange-500">Akadeemia tee 5, 12616 Tallinn, Eesti</a>
<br/>
E-post: <a href="mailto:lapikud@lapikud.ee" style="color: var(--orange)">lapikud@lapikud.ee</a>
E-post: <a href="mailto:lapikud@lapikud.ee" class="text-orange-500">lapikud@lapikud.ee</a>
<br/>
Tel: <a href="tel:+37258160799" style="color: var(--orange)">+372 58 160 799</a>
Tel: <a href="tel:+37258160799" class="text-orange-500">+372 58 160 799</a>
<br/>
Messenger: <a href="https://m.me/Lapikud" target="_blank" rel="noopener noreferrer" style="color: var(--orange)">m.me/Lapikud</a>
Messenger: <a href="https://m.me/Lapikud" target="_blank" rel="noopener noreferrer" class="text-orange-500">m.me/Lapikud</a>
</address>
<div>
Reg. kood: <a href="https://ariregister.rik.ee/est/company/80167145/" style="color: var(--orange)">80167145</a>
Reg. kood: <a href="https://ariregister.rik.ee/est/company/80167145/" class="text-orange-500">80167145</a>
<br/>
Swedbank EE812200221019551756
</div>
@@ -31,26 +32,20 @@ import {
</div>
<div>
<Container>
<h3 class="text-3xl font-light flex justify-center" style="padding-bottom: var(--space-4);">Sotsiaalmeedia</h3>
<div class="flex items-center justify-center" style="gap: var(--space-4);">
<h3 class="text-3xl font-light flex justify-center pb-4">Sotsiaalmeedia</h3>
<div class="flex items-center justify-center gap-4">
<a href="https://www.instagram.com/lapikud/" target="_blank" rel="noopener noreferrer" aria-label="Instagram" class="transition-opacity duration-200">
<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
</svg>
<Svg type="instagram"/>
</a>
<a href="https://www.facebook.com/Lapikud" target="_blank" rel="noopener noreferrer" aria-label="Facebook" class="transition-opacity duration-200">
<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
<Svg type="facebook"/>
</a>
<a href="https://github.com/lapikud" target="_blank" rel="noopener noreferrer" aria-label="GitHub" class="transition-opacity duration-200">
<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
<Svg type="github"/>
</a>
</div>
</Container>
</div>
</Grid>
<Center><small style="color: var(--orange); padding-top: var(--space-6);">© 2026 MTÜ Lapikud</small></Center>
<Center><small class="text-orange-500 pt-6">© 2026 MTÜ Lapikud</small></Center>
</Section>

View File

@@ -105,7 +105,7 @@
}
</style>
<Section className="text-black" background="orange" padding="tight">
<Section className="text-black" bg="bg-orange-500" padding="tight">
{#if $text && Object.keys($text).length > 0}
<div class="flex items-center w-full relative min-h-12">
<!-- Logo -->
@@ -122,50 +122,47 @@
<nav class="z-10 hidden md:flex gap-4 justify-end text-xl">
<Dropdown
name={$text.about || 'Meist'}
buttonClass="rounded-[5px]"
buttonHoverStyle="color:var(--white)"
panelClassName="bg-[var(--white)] text-[var(--black)]"
buttonClass="rounded-[5px] hover:text-white"
panelClassName="bg-white text-black"
>
<Button class="rounded-[5px] bg-transparent" buttonHoverStyle="color:var(--orange)" onClick={() => navigate("/lapikutest")}
<Button class="rounded-[5px] bg-transparent hover:text-orange-500" onClick={() => navigate("/lapikutest")}
>{$text.aboutPages.info}</Button
>
<Button class="rounded-[5px] bg-transparent" buttonHoverStyle="color:var(--orange)" onClick={() => navigate("/tudengile")}
<Button class="rounded-[5px] bg-transparent hover:text-orange-500" onClick={() => navigate("/tudengile")}
><Bot />{$text.aboutPages.join}</Button
>
<Button class="rounded-[5px] bg-transparent" buttonHoverStyle="color:var(--orange)" onClick={() => navigate("/mentorid")}
<Button class="rounded-[5px] bg-transparent hover:text-orange-500" onClick={() => navigate("/mentorid")}
><HandHeart />{$text.aboutPages.mentors}</Button
>
<Button class="rounded-[5px] bg-transparent" buttonHoverStyle="color:var(--orange)" onClick={() => navigate("/juhatus")}
<Button class="rounded-[5px] bg-transparent hover:text-orange-500" onClick={() => navigate("/juhatus")}
><Lectern />{$text.aboutPages.board}</Button
>
</Dropdown>
<Dropdown
name={$text.events}
buttonClass="rounded-[5px]"
buttonHoverStyle="color:var(--white)"
panelClassName="bg-[var(--white)] text-[var(--black)]"
buttonClass="rounded-[5px] hover:text-white"
panelClassName="bg-white text-black"
>
<Button class="rounded-[5px] bg-transparent" buttonHoverStyle="color:var(--orange)" onClick={() => navigate("/koolitused")}
<Button class="rounded-[5px] bg-transparent hover:text-orange-500" onClick={() => navigate("/koolitused")}
><Presentation />{$text.eventsPages.workshops}</Button
>
<Button class="rounded-[5px] bg-transparent" buttonHoverStyle="color:var(--orange)" onClick={() => navigate("https://asikarikas.ee/", { external: true })}
<Button class="rounded-[5px] bg-transparent hover:text-orange-500" onClick={() => navigate("https://asikarikas.ee/", { external: true })}
><Trophy />ASI Karikas</Button
>
<Button class="rounded-[5px] bg-transparent" buttonHoverStyle="color:var(--orange)" onClick={() => navigate("https://remondikohvik.lapikud.ee/", { external: true })}
<Button class="rounded-[5px] bg-transparent hover:text-orange-500" onClick={() => navigate("https://remondikohvik.lapikud.ee/", { external: true })}
><Coffee />{$text.eventsPages.repair}</Button
>
</Dropdown>
<Button buttonHoverStyle="color:var(--white)" class="rounded-[5px]" onClick={() => navigate("/kontakt")}
<Button class="rounded-[5px] hover:text-white" onClick={() => navigate("/kontakt")}
>{$text.contact}
</Button>
<Button buttonHoverStyle="color:var(--white)" class="rounded-[5px]" onClick={() => navigate("/helpdesk")}
<Button class="rounded-[5px] hover:text-white" onClick={() => navigate("/helpdesk")}
>{$text.helpdesk}
</Button>
<!-- Language Switcher -->
<Button
class="rounded-[5px] px-3 py-1 text-sm ml-2"
buttonHoverStyle="color:var(--white)"
class="rounded-[5px] px-3 py-1 text-sm ml-2 hover:text-white"
onClick={() => switchLanguageRoute($currentLang === 'est' ? 'en' : 'est')}
>
{$currentLang === 'est' ? 'EN' : 'EST'}
@@ -175,8 +172,7 @@
<!-- Mobile Hamburger Button - Fixed position -->
<Button
onClick={toggleMobileMenu}
class="md:hidden p-2 text-white transition-colors bg-transparent absolute right-0"
buttonHoverStyle="color: var(--white)"
class="md:hidden p-2 text-white transition-colors bg-transparent absolute right-0 hover:text-white"
style="opacity: {mobileMenuOpen ? '0' : '1'}; pointer-events: {mobileMenuOpen ? 'none' : 'auto'};"
aria-label="Toggle menu"
>
@@ -198,8 +194,7 @@
<div class="absolute top-8 right-0 px-(--site-padding)">
<Button
onClick={closeMobileMenu}
class="ml-auto p-2 transition-colors flex bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="ml-auto p-2 transition-colors flex bg-transparent hover:text-orange-500"
aria-label="Close menu"
>
<X size={32} color="white" />
@@ -207,38 +202,34 @@
</div>
<!-- Menu Content -->
<Stack gap="var(--space-5)" className="flex-1 px-8 pt-24 pb-12 text-white text-xl">
<Stack gap="gap-5" className="flex-1 px-8 pt-24 pb-12 text-white text-xl">
<!-- About Section -->
<Stack gap="var(--space-3)">
<h3 class="text-2xl font-bold" style="color: var(--orange)">{$text.about}</h3>
<Stack gap="var(--space-2)" className="pl-4">
<Stack gap="gap-3">
<h3 class="text-2xl font-bold text-orange-500">{$text.about}</h3>
<Stack gap="gap-2" className="pl-4">
<Button
onClick={() => handleNavigation("/lapikutest")}
class="text-left transition-colors flex items-center gap-2 bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left transition-colors flex items-center gap-2 bg-transparent hover:text-orange-500"
>
{$text.aboutPages.info}
</Button>
<Button
onClick={() => handleNavigation("/tudengile")}
class="text-left transition-colors flex items-center gap-2 bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left transition-colors flex items-center gap-2 bg-transparent hover:text-orange-500"
>
<Bot size={20} />
{$text.aboutPages.join}
</Button>
<Button
onClick={() => handleNavigation("/mentorid")}
class="text-left transition-colors flex items-center gap-2 bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left transition-colors flex items-center gap-2 bg-transparent hover:text-orange-500"
>
<HandHeart size={20} />
{$text.aboutPages.mentors}
</Button>
<Button
onClick={() => handleNavigation("/juhatus")}
class="text-left transition-colors flex items-center gap-2 bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left transition-colors flex items-center gap-2 bg-transparent hover:text-orange-500"
>
<Lectern size={20} />
{$text.aboutPages.board}
@@ -247,29 +238,26 @@
</Stack>
<!-- Events Section -->
<Stack gap="var(--space-3)" >
<h3 class="text-2xl font-bold" style="color: var(--orange)">{$text.events}</h3>
<Stack gap="var(--space-2)" className="pl-4">
<Stack gap="gap-3" >
<h3 class="text-2xl font-bold text-orange-500">{$text.events}</h3>
<Stack gap="gap-2" className="pl-4">
<Button
onClick={() => handleNavigation("/mentorid")}
class="text-left transition-colors flex items-center gap-2 bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left transition-colors flex items-center gap-2 bg-transparent hover:text-orange-500"
>
<Presentation size={20} />
{$text.eventsPages.workshops}
</Button>
<Button
onClick={() => handleNavigation("https://asikarikas.ee/", { external: true })}
class="text-left transition-colors flex items-center gap-2 bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left transition-colors flex items-center gap-2 bg-transparent hover:text-orange-500"
>
<Trophy size={20} />
ASI Karikas
</Button>
<Button
onClick={() => handleNavigation("https://remondikohvik.lapikud.ee/", { external: true })}
class="text-left transition-colors flex items-center gap-2 bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left transition-colors flex items-center gap-2 bg-transparent hover:text-orange-500"
>
<Coffee size={20} />
{$text.eventsPages.repair}
@@ -278,27 +266,24 @@
</Stack>
<!-- Direct Links -->
<Stack gap="var(--space-3)">
<Stack gap="gap-3">
<Button
onClick={() => handleNavigation("/kontakt")}
class="text-left text-2xl font-semibold transition-colors bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left text-2xl font-semibold transition-colors bg-transparent hover:text-orange-500"
>
{$text.contact}
</Button>
<Button
onClick={() => handleNavigation("/helpdesk")}
class="text-left text-2xl font-semibold transition-colors bg-transparent"
buttonHoverStyle="color: var(--orange)"
class="text-left text-2xl font-semibold transition-colors bg-transparent hover:text-orange-500"
>
{$text.helpdesk}
</Button>
</Stack>
<!-- Language Switcher -->
<Stack gap="var(--space-3)">
<Stack gap="gap-3">
<Button
class="w-full rounded-lg py-3 text-lg"
style="background-color: var(--orange); color: var(--black)"
class="w-full rounded-lg py-3 text-lg bg-orange-500 text-black"
onClick={() => handleLanguageSwitch($currentLang === 'est' ? 'en' : 'est')}
>
{$currentLang === 'est' ? 'English' : 'Eesti'}

View File

@@ -25,12 +25,12 @@
</script>
<!-- Header Section -->
<Section background="orange">
<Section bg="bg-orange-500">
<Grid>
<Center>
<img src="/assets/LapLogo_black_white.png" alt="MTÜ Lapikud" class="max-w-md w-full h-auto" style="padding: var(--space-4);"/>
<img src="/assets/LapLogo_black_white.png" alt="MTÜ Lapikud" class="max-w-md w-full h-auto p-4"/>
</Center>
<p class="text-3xl leading-relaxed" style="padding: var(--space-4);">
<p class="text-3xl leading-relaxe p-4">
MTÜ Lapikud on Tallinna Tehnikaülikooli Tarkvaraarendusklubi, mis ühendab endisi
ja praegusi IT huvilisi tudengeid.
</p>
@@ -38,42 +38,42 @@
</Section>
<Section>
<Grid gap="var(--space-5)">
<Grid gap="gap-5">
<Card variant="blur" href="/tudengile">
<div class="feature-icon" style="color: var(--orange)">
<div class="feature-icon text-orange-500">
<Users size={32} strokeWidth={1.5} />
</div>
<h3 class="feature-title">Tudengile</h3>
<p class="feature-description">
Tule arenda oma oskuseid ja saa ägedaid sõpru! Omanda praktilist kogemust reaalsetest projektidest.
</p>
<div style="color: var(--orange)" class="mt-4 font-semibold flex items-center gap-2">
<div class="mt-4 font-semibold flex items-center gap-2 text-orange-500">
Liitu meiega <ArrowRight size={16} />
</div>
</Card>
<Card variant="blur" href="/helpdesk">
<div class="feature-icon" style="color: var(--orange)">
<div class="feature-icon text-orange-500">
<Wrench size={32} strokeWidth={1.5} />
</div>
<h3 class="feature-title">Helpdesk</h3>
<p class="feature-description">
HELPDESK on MTÜ Lapikute poolt pakutav arvutiabiteenus. Teenus on suunatud tudengitele, õppejõududele ning kõikidele huvilistele.
</p>
<div style="color: var(--orange)" class="mt-4 font-semibold flex items-center gap-2">
<div class="mt-4 font-semibold flex items-center gap-2 text-orange-500">
Vaata teenuseid <ArrowRight size={16} />
</div>
</Card>
<Card variant="blur" href="/ourwork">
<div class="feature-icon" style="color: var(--orange)">
<div class="feature-icon text-orange-500">
<HeartHandshake size={32} strokeWidth={1.5} />
</div>
<h3 class="feature-title">Ettevõttele</h3>
<p class="feature-description">
Aitame sinu ideed ellu viia! Meie kogenud tudengid on valmis teie projekte realiseerima.
</p>
<div style="color: var(--orange)" class="mt-4 font-semibold flex items-center gap-2">
<div class="mt-4 font-semibold flex items-center gap-2 text-orange-500">
Tehtud tööd <ArrowRight size={16} />
</div>
</Card>
@@ -82,7 +82,7 @@
<!-- What We Do Section -->
<Section>
<Grid gap="var(--space-4)">
<Grid gap="gap-4">
<Card variant="blur">
<h2 class="pb-4 text-4xl font-bold">Mida me loome</h2>
<p>Meie põhieesmärgiks on Tallinna Tehnikaülikooli IT tudengitele erialase lisandväärtuse loomine. Kaasame tudengeid praktilistesse lahendustesse, kus saab ideid reaalselt ellu viia ja oma oskusi päriselt kasutada. Meie projektidest kasvavad välja huvitavad ja tarvilikud lõputööd, millel on selge praktiline väärtus.</p>
@@ -97,7 +97,7 @@
<!-- Partners Section -->
<Section>
<div class="text-center mb-12">
<h2 class="text-5xl">
<h2 class="text-5xl text-orange-500">
Koostööpartnerid
</h2>
</div>
@@ -107,7 +107,7 @@
{#if partner.url}
<a href={partner.url} target="_blank" rel="noopener noreferrer" class="partner-item" title={partner.name}>
{#if partner.image}
<img src={`/assets/partners_logos/${partner.image}`} alt={partner.name} />
<img src={`/assets/partners/${partner.image}`} alt={partner.name} />
{:else}
<span class="partner-name">{partner.name}</span>
{/if}
@@ -115,7 +115,7 @@
{:else}
<div class="partner-item">
{#if partner.image}
<img src={`/assets/partners_logos/${partner.image}`} alt={partner.name} />
<img src={`/assets/partners/${partner.image}`} alt={partner.name} />
{:else}
<span class="partner-name">{partner.name}</span>
{/if}
@@ -126,24 +126,7 @@
</Section>
<style>
.feature-icon {
margin-bottom: var(--space-3);
display: inline-flex;
}
.feature-title {
font-size: var(--fs-xl);
font-weight: 700;
margin-bottom: var(--space-2);
color: var(--black);
}
.feature-description {
color: var(--gray-800);
line-height: 1.6;
margin-bottom: var(--space-3);
}
/* PARTNERS SECTION */
.partner-item {
display: flex;
align-items: center;

View File

@@ -1,51 +0,0 @@
import Icon from '../components/Svg.svelte';
export default {
title: 'SVG/Icon',
component: Icon,
argTypes: {
type: { control: { type: 'select', options: ['connector','mirrorV','mirrorH','mirrorVH','branch','branchH'] } },
className: { control: 'text' },
},
};
const Template = (args) => ({
Component: Icon,
props: args,
});
export const Default = Template.bind({});
Default.args = {
type: 'connector',
className: 'w-64 text-cyan-500',
};
export const MirrorV = Template.bind({});
MirrorV.args = {
type: 'mirrorV',
className: 'w-64 text-green-500',
};
export const MirrorH = Template.bind({});
MirrorH.args = {
type: 'mirrorH',
className: 'w-64 text-yellow-500',
};
export const MirrorVH = Template.bind({});
MirrorVH.args = {
type: 'mirrorVH',
className: 'w-64 text-pink-500',
};
export const Branch = Template.bind({});
Branch.args = {
type: 'branch',
className: 'w-48 text-magenta-500',
};
export const BranchH = Template.bind({});
BranchH.args = {
type: 'branchH',
className: 'w-48 text-magenta-500',
};

View File

@@ -0,0 +1,16 @@
<script context="module">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Svg from '../components/Svg.svelte';
const { Story } = defineMeta({
title: 'Components/Svg',
component: Svg,
});
</script>
<Story name="connector" args={{ type: 'connector' }} />
<Story name="mirrorV" args={{ type: 'mirrorV' }} />
<Story name="mirrorH" args={{ type: 'mirrorH' }} />
<Story name="mirrorVH" args={{ type: 'mirrorVH' }} />
<Story name="branch" args={{ type: 'branch' }} />
<Story name="branchH" args={{ type: 'branchH' }} />

View File

@@ -4,39 +4,5 @@ export default {
"./index.html",
"./src/**/*.{svelte,js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
orange: {
DEFAULT: '#f0941d',
light: '#ffa940',
dark: '#d67d0a',
500: '#f0941d',
},
gray: {
50: '#fafafa',
100: '#f5f5f5',
200: '#e5e5e5',
300: '#d4d4d4',
800: '#262626',
900: '#171717',
},
},
spacing: {
'0': '0px',
'1': '4px',
'2': '8px',
'3': '16px',
'4': '24px',
'5': '32px',
'6': '48px',
},
maxWidth: {
'2xl': '700px',
'3xl': '800px',
'4xl': '900px',
},
},
},
plugins: [],
}