updated and fixed most pages + navbar update on home page

This commit is contained in:
Henri
2026-04-22 20:59:29 +03:00
parent 83e090ef8f
commit 08d6e08c4d
12 changed files with 168 additions and 213 deletions

View File

@@ -1,6 +1,6 @@
/* Global stylesheet */
@import url("https://fonts.googleapis.com/css2?family=Unbounded:wght@300;400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700;900&display=swap");
@import "tailwindcss";
/* Overwriting tailwindcss specific variables */
@@ -46,12 +46,7 @@
--spacing-6: 48px;
/* type scale (fluid base + modular sizes) */
--fs-base: clamp(0.95rem, 0.9vw + 0.9rem, 1rem);
--fs-xs: 0.85rem;
--fs-sm: 0.92rem;
--fs-md: 1rem;
--fs-lg: 1.125rem;
--fs-xl: 1.5rem;
--fs-base: clamp(1.1rem, 1vw + 1rem, 1.2rem);
--line-height: 1.6;
}
@@ -62,7 +57,7 @@
box-sizing: border-box;
}
html {
font-family: "Unbounded", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
font-family: "Lato", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
Roboto, "Helvetica Neue", Arial;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

View File

@@ -9,6 +9,7 @@
} = $props();
let isOpen = $state(false);
let panelElement = $state(null);
const toggle = () => (isOpen = !isOpen);
const close = () => (isOpen = false);
@@ -21,6 +22,26 @@
return;
close();
}
$effect(() => {
if (!isOpen) return;
const handleDocumentClick = (event) => {
const target = event.target;
if (!(target instanceof HTMLElement) || !panelElement) return;
if (
panelElement.contains(target) &&
target.closest("button, a, [role='menuitem']")
) {
close();
}
};
document.addEventListener("click", handleDocumentClick);
return () => document.removeEventListener("click", handleDocumentClick);
});
</script>
<div class="relative inline-block" onfocusout={handleFocusOut}>
@@ -38,6 +59,7 @@
<div
class="{panelClass} absolute mt-2 rounded-md shadow-lg overflow-visible flex flex-col"
style:visibility={isOpen ? "visible" : "hidden"}
bind:this={panelElement}
>
<div>
{@render children?.()}

View File

@@ -44,63 +44,19 @@
return paths.includes(currentPath);
}
function isHomePage() {
return currentPath === "/";
}
function headerShellClass() {
return isHomePage()
? "bg-transparent"
: "border-b-2 border-orange-500 bg-gray-900 backdrop-blur-md";
}
function headerRowClass() {
return isHomePage()
? "flex min-h-16 items-center w-full relative gap-4"
: "flex min-h-16 items-center gap-4";
}
function headerWrapClass() {
return isHomePage()
? "ml-auto flex w-full self-stretch items-center justify-end md:w-auto md:border-b-3 md:border-white backdrop-blur-lg bg-black/35 pr-(--page-padding-inline)"
: "flex items-center justify-end ml-auto w-full md:w-auto";
}
function desktopNavClass() {
return isHomePage()
? "z-10 hidden md:flex gap-1 justify-end text-lg"
: "z-10 hidden md:flex items-center gap-1 justify-end text-lg";
}
function navButtonClass(active = false) {
if (isHomePage()) {
return active
? "border-transparent text-orange-500 bg-transparent"
: "border-transparent bg-transparent text-white/75 hover:text-orange-500";
}
return active
? "border-transparent text-orange-500 hover:bg-white/5"
: "border-transparent text-white/75 hover:bg-white/5";
}
function dropdownButtonClass(active = false) {
if (isHomePage()) {
return active
? "border-transparent text-orange-500 bg-transparent"
: "border-transparent bg-transparent text-white/75 hover:text-orange-500";
}
return active
? "border-transparent text-orange-500 hover:bg-white/5"
: "border-transparent text-white/75 hover:bg-white/5";
}
function languageButtonClass() {
if (isHomePage()) {
return "ml-3 border-transparent bg-transparent px-3 py-1 text-sm text-white/75 hover:text-orange-500";
}
return "ml-3 border-transparent bg-transparent px-3 py-1 text-sm text-white/75 hover:bg-white/5";
}
@@ -217,21 +173,19 @@
</style>
<header class="fixed inset-x-0 top-0 z-50 text-white">
<div class={headerShellClass()}>
<Container maxWidth={true} center={!isHomePage()} class="py-0">
<div class="border-b-2 border-orange-500 bg-gray-900 backdrop-blur-md">
<Container class="py-0">
{#if $text && Object.keys($text).length > 0}
<div class={headerRowClass()}>
{#if !isHomePage()}
<Button
onClick={() => handleNavigation("/")}
class="nav-logo-button border-transparent bg-transparent px-0 py-3"
>
<img src="/assets/logo.svg" alt="Lapikud logo" class="h-12 md:h-14 w-auto" />
</Button>
{/if}
<div class="flex min-h-16 items-center gap-4">
<Button
onClick={() => handleNavigation("/")}
class="nav-logo-button border-transparent bg-transparent px-0 py-3"
>
<img src="/assets/logo.svg" alt="Lapikud logo" class="h-12 md:h-14 w-auto" />
</Button>
<div class={headerWrapClass()}>
<nav class={desktopNavClass()}>
<div class="ml-auto flex w-full items-center justify-end md:w-auto">
<nav class="z-10 hidden items-center gap-1 justify-end text-lg md:flex">
<Dropdown
name={$text.about}
buttonClass={`${dropdownButtonClass(isInGroup(aboutRoutes))} ${isInGroup(aboutRoutes) ? "nav-active-parent" : ""}`}
@@ -309,7 +263,7 @@
</Button>
<Button
class={languageButtonClass()}
class="ml-3 border-transparent bg-transparent px-3 py-1 text-sm text-white/75 hover:bg-white/5"
onClick={() => switchLanguageRoute($currentLang === "est" ? "en" : "est")}
>
{$currentLang === "est" ? "EN" : "EST"}

View File

@@ -11,7 +11,23 @@
let { routes } = $props();
let currentPath = $state(window.location.pathname);
let CurrentComponent = $derived(routes[currentPath] || routes["/"]);
let CurrentComponent = $state(null);
async function resolveCurrentComponent(path) {
const routeEntry = routes[path] || routes["/"];
if (typeof routeEntry === "function") {
try {
const module = await routeEntry();
CurrentComponent = module?.default || null;
} catch {
CurrentComponent = null;
}
return;
}
CurrentComponent = routeEntry || null;
}
function navigate(path) {
window.history.pushState({}, "", path);
@@ -46,6 +62,10 @@
return () => window.removeEventListener("click", handleClick);
});
$effect(() => {
resolveCurrentComponent(currentPath);
});
</script>
{#if CurrentComponent}

View File

@@ -54,12 +54,7 @@
</div>
<div class="pointer-events-none absolute inset-y-0 left-1/2 w-screen -translate-x-1/2 bg-linear-to-r from-black/65 via-black/35 to-transparent" aria-hidden="true"></div>
<div class="relative z-20 flex min-h-screen w-full items-stretch py-8 md:py-12">
<div class="flex w-full max-w-115 flex-col justify-between">
<Image
src="/assets/logo.svg"
alt={$text["hero"]?.logoAlt || "MTÜ Lapikud"}
class="w-full max-w-65 md:max-w-80 h-auto pt-5 md:pt-10"
/>
<div class="flex w-full max-w-115 flex-col justify-end">
<div class="flex flex-col items-start gap-5">
<p class="w-full max-w-110 text-left text-lg leading-[165%] text-white drop-shadow-[0_2px_8px_rgba(0,0,0,0.6)] md:max-w-125 md:text-xl">
{$text["hero"]?.description || "MTÜ Lapikud on Tallinna Tehnikaülikooli Tarkvaraarendusklubi, mis ühendab endisi ja praegusi IT huvilisi tudengeid."}

View File

@@ -105,7 +105,7 @@
</Section>
<Section padding="large">
<div class="mb-12">
<div class="mb-22">
<p class="mb-3 text-[0.72rem] font-bold uppercase tracking-[0.12em] text-orange-500">{$text["portfolio"]?.label || "Tehtud tööd"}</p>
<h1 class="m-0 max-w-[20ch] text-[clamp(1.9rem,4vw,3.15rem)] leading-[1.08] tracking-[-0.03em]">{$text["portfolio"]?.title || "Projektid, mis räägivad enda eest."}</h1>
<p class="mt-4 max-w-[62ch] leading-[1.65] text-gray-900">
@@ -114,13 +114,13 @@
</div>
{#if featuredProject}
<Card class="mb-5! grid! overflow-hidden! rounded-none! border-0! bg-white! p-0! ring-1 ring-gray-200 md:grid-cols-[1.25fr_1fr]!" variant="default">
<div class="min-h-[220px] overflow-hidden bg-gray-100/70 p-2 md:min-h-[280px]">
<Card class="mb-5! grid! gap-4 rounded-none! border-0! bg-white! p-0! md:grid-cols-[1.25fr_1fr]! md:gap-6" variant="default">
<div class="flex min-h-[220px] items-center justify-center md:min-h-[280px]">
{#if featuredProject.photo}
<img
src={projectImage(featuredProject)}
alt={projectTitle(featuredProject)}
class="block h-full w-full border border-gray-200 bg-white object-contain object-center shadow-[0_1px_2px_rgba(13,13,13,0.05)]"
class="block h-auto max-h-full w-auto max-w-full object-contain object-center shadow-[0_1px_2px_rgba(13,13,13,0.05)]"
on:error={(event) => handleImageError(event, featuredProject.photo)}
/>
{:else}
@@ -128,17 +128,19 @@
{/if}
</div>
<div class="flex flex-col justify-center gap-2 p-[clamp(1.5rem,3vw,2.5rem)]">
<h2 class="m-0 text-[clamp(1.45rem,2.7vw,2rem)] leading-[1.2] tracking-[-0.02em]">{projectTitle(featuredProject)}</h2>
{#if projectDescription(featuredProject)}
<p class="m-0 text-[0.9rem] leading-[1.6] text-gray-900">{projectDescription(featuredProject)}</p>
{/if}
<div class="flex h-full flex-1 flex-col justify-between gap-[clamp(0.75rem,2vw,1.5rem)] p-[clamp(1.5rem,3vw,2.5rem)]">
<div class="flex flex-col gap-[clamp(0.45rem,1vw,0.8rem)]">
<h2 class="m-0 text-[clamp(1.45rem,2.7vw,2rem)] leading-[1.2] tracking-[-0.02em]">{projectTitle(featuredProject)}</h2>
{#if projectDescription(featuredProject)}
<p class="m-0 text-[0.9rem] leading-[1.6] text-gray-900">{projectDescription(featuredProject)}</p>
{/if}
</div>
{#if featuredProject.url}
<a
href={featuredProject.url}
target="_blank"
rel="noopener noreferrer"
class="mt-0.5 inline-flex w-fit items-center gap-1.5 font-semibold text-orange-500 hover:underline"
class="mt-auto inline-flex w-fit items-center gap-1.5 pt-1 font-semibold text-orange-500 hover:underline"
>
{$text["portfolio"]?.projectLink || "Vaata projekti"}
<ExternalLink size={16} />
@@ -149,13 +151,13 @@
{/if}
<div class="ourwork-grid">
{#each regularProjects as project}
<Card variant="default" class="ourwork-grid-card rounded-none! border-0! bg-white! p-0!">
<div class="aspect-16/10 overflow-hidden bg-gray-100/70 p-2">
<Card variant="default" class="ourwork-grid-card gap-4 rounded-none! border-0! bg-white! p-0!">
<div class="flex aspect-16/10 items-center justify-center">
{#if project.photo}
<img
src={projectImage(project)}
alt={projectTitle(project)}
class="block h-full w-full border border-gray-200 bg-white object-contain object-center shadow-[0_1px_2px_rgba(13,13,13,0.05)]"
class="block h-auto max-h-full w-auto max-w-full object-contain object-center shadow-[0_1px_2px_rgba(13,13,13,0.05)]"
on:error={(event) => handleImageError(event, project.photo)}
/>
{:else}
@@ -163,13 +165,15 @@
{/if}
</div>
<div class="ourwork-card-content flex h-full flex-1 flex-col gap-2 p-5">
<h3 class="ourwork-card-title m-0 text-base leading-[1.2] tracking-[-0.02em]">{projectTitle(project)}</h3>
{#if projectDescription(project)}
<p class="ourwork-card-description m-0 text-[0.9rem] leading-[1.6] text-gray-900">{projectDescription(project)}</p>
{:else}
<p class="ourwork-card-description m-0 text-[0.9rem] leading-[1.6] text-gray-900 italic">{$text["portfolio"]?.noDescription || "Kirjeldus lisamisel."}</p>
{/if}
<div class="ourwork-card-content flex h-full flex-1 flex-col justify-between gap-[clamp(0.55rem,1vw,0.95rem)] px-5 pb-5 pt-1.5">
<div class="flex flex-col gap-[clamp(0.4rem,0.9vw,0.75rem)]">
<h3 class="ourwork-card-title m-0 text-base leading-[1.2] tracking-[-0.02em]">{projectTitle(project)}</h3>
{#if projectDescription(project)}
<p class="ourwork-card-description m-0 text-[0.9rem] leading-[1.6] text-gray-900">{projectDescription(project)}</p>
{:else}
<p class="ourwork-card-description m-0 text-[0.9rem] leading-[1.6] text-gray-900 italic">{$text["portfolio"]?.noDescription || "Kirjeldus lisamisel."}</p>
{/if}
</div>
{#if project.url}
<a
@@ -191,10 +195,10 @@
<style>
.ourwork-grid {
display: flex;
flex-wrap: wrap;
gap: 1px;
justify-content: center;
display: grid;
gap: clamp(0.75rem, 1.5vw, 1.1rem);
align-items: stretch;
grid-template-columns: 1fr;
}
.ourwork-grid :global(.ourwork-grid-card) {
@@ -205,37 +209,15 @@
width: 100%;
}
.ourwork-card-content {
min-height: 10.25rem;
}
.ourwork-card-title {
min-height: calc(1.2em * 2);
display: -webkit-box;
line-clamp: 2;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.ourwork-card-description {
min-height: calc(1.6em * 3);
display: -webkit-box;
line-clamp: 3;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
@media (min-width: 768px) {
.ourwork-grid :global(.ourwork-grid-card) {
width: calc((100% - 1px) / 2);
.ourwork-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1200px) {
.ourwork-grid :global(.ourwork-grid-card) {
width: calc((100% - 2px) / 3);
.ourwork-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
</style>

View File

@@ -51,7 +51,7 @@
</script>
<div class="safe-area-navbar">
<Section class="overflow-hidden bg-[#0f0f0f]" padding="none" fullWidth={true} contentClass="relative !px-0">
<Section class="overflow-hidden bg-gray-900" padding="none" fullWidth={true} contentClass="relative !px-0">
<div class="relative overflow-hidden py-[clamp(3rem,8vw,6rem)]">
<div class="relative z-10 mx-auto w-full max-w-(--page-max-width) px-(--page-padding-inline)">
<div class="relative z-10 w-full max-w-3xl">

View File

@@ -1,5 +1,5 @@
<script>
import { Section, Stack, Container } from "$components";
import { Section, Stack, Image } from "$components";
import { onMount, onDestroy } from "svelte";
import yaml from "js-yaml";
import { currentLang, getLangText, createPageTextStore } from "$lib";
@@ -22,6 +22,10 @@
return getLangText(workshop, field, $currentLang);
}
function getGallery(workshop) {
return Array.isArray(workshop?.gallery) ? workshop.gallery : [];
}
function getGalleryClass(count) {
const classes = {
1: "grid-cols-1 grid-rows-1",
@@ -29,7 +33,7 @@
3: "grid-cols-2 grid-rows-2",
4: "grid-cols-2 grid-rows-2",
};
return classes[count] || classes[1];
return classes[count] || "grid-cols-2";
}
function getImageHeightClass(count, index) {
@@ -39,13 +43,13 @@
return index === 0 ? "h-72 row-span-2" : "h-32";
}
if (count === 4) return "h-32";
return "h-40";
return "h-32";
}
</script>
<div class="safe-area-navbar">
<!-- Hero Section -->
<Section bg="bg-black" class="text-white">
<Section bg="bg-gray-900" class="text-white">
<div class="max-w-2xl">
<p class="font-dm-mono text-xs uppercase tracking-widest text-orange-500 mb-4">
{$text["hero"]?.label}
@@ -66,29 +70,23 @@
<Section>
<Stack gap="lg">
{#each workshops as workshop, index (index)}
<div class={`grid grid-cols-1 md:grid-cols-2 gap-14 md:gap-16 py-16 border-b border-gray-100 ${index === workshops.length - 1 ? "border-b-0" : ""}`}>
<div class={`grid grid-cols-1 md:grid-cols-2 gap-14 md:gap-16 py-16 border-b border-orange-500 ${index === workshops.length - 1 ? "border-b-0" : ""}`}>
<!-- Gallery -->
<div
class={`grid gap-1.5 ${index % 2 === 1 ? "md:order-2" : ""}`}
>
<div class={`grid ${getGalleryClass(workshop.gallery)} gap-1.5`}>
{#each Array(workshop.gallery) as _, i}
<div class={`grid ${getGalleryClass(getGallery(workshop).length)} gap-1.5`}>
{#each getGallery(workshop) as filename, i (filename)}
<div
class={`bg-gray-100 overflow-hidden flex items-center justify-center ${getImageHeightClass(workshop.gallery, i)}`}
class={`overflow-hidden ${getImageHeightClass(getGallery(workshop).length, i)}`}
>
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1"
class="text-gray-300"
>
<rect x="3" y="3" width="18" height="18" rx="1" />
<circle cx="8.5" cy="8.5" r="1.5" />
<polyline points="21 15 16 10 5 21" />
</svg>
<Image
src={`/assets/workshops/${filename}`}
alt={`${getField(workshop, "title")} ${i + 1}`}
objectFit="cover"
class="h-full w-full"
pictureClass="h-full w-full"
/>
</div>
{/each}
</div>

View File

@@ -1,17 +1,17 @@
// ADD PAGES HERE TO REGISTER ROUTES
// examples: '/page', '/page/:id'
import Home from './Home.svelte'
import Student from './Student.svelte'
import Mentors from './Mentors.svelte'
import Helpdesk from './Helpdesk.svelte'
import OurWork from './OurWork.svelte'
import AboutUs from './AboutUs.svelte'
import Contact from './Contact.svelte'
import Managment from './Managment.svelte'
import Striim from './Striim.svelte'
import Calendar from './Calendar.svelte'
import Workshops from './Workshops.svelte';
const loadHome = () => import('./Home.svelte');
const loadStudent = () => import('./Student.svelte');
const loadMentors = () => import('./Mentors.svelte');
const loadHelpdesk = () => import('./Helpdesk.svelte');
const loadOurWork = () => import('./OurWork.svelte');
const loadAboutUs = () => import('./AboutUs.svelte');
const loadContact = () => import('./Contact.svelte');
const loadManagment = () => import('./Managment.svelte');
const loadStriim = () => import('./Striim.svelte');
const loadCalendar = () => import('./Calendar.svelte');
const loadWorkshops = () => import('./Workshops.svelte');
// Route mapping for language switching
export const routeMap = {
@@ -91,23 +91,23 @@ export function getTranslatedRoute(currentPath, targetLang) {
}
export const routes = {
'/': Home,
'/tudengile': Student,
'/student': Student,
'/mentorid': Mentors,
'/mentors': Mentors,
'/helpdesk': Helpdesk,
'/ettevottele': OurWork,
'/ourwork': OurWork,
'/lapikutest': AboutUs,
'/aboutus': AboutUs,
'/kontakt': Contact,
'/contact': Contact,
'/juhatus': Managment,
'/management': Managment,
'/koolitused': Workshops,
'/workshops': Workshops,
'/striim': Striim,
'/kalender': Calendar,
'/calendar': Calendar,
'/': loadHome,
'/tudengile': loadStudent,
'/student': loadStudent,
'/mentorid': loadMentors,
'/mentors': loadMentors,
'/helpdesk': loadHelpdesk,
'/ettevottele': loadOurWork,
'/ourwork': loadOurWork,
'/lapikutest': loadAboutUs,
'/aboutus': loadAboutUs,
'/kontakt': loadContact,
'/contact': loadContact,
'/juhatus': loadManagment,
'/management': loadManagment,
'/koolitused': loadWorkshops,
'/workshops': loadWorkshops,
'/striim': loadStriim,
'/kalender': loadCalendar,
'/calendar': loadCalendar,
}