footer, navbar and components rework

This commit is contained in:
Henri
2026-01-23 21:57:05 +02:00
parent f3a3c4e042
commit 3e38e0e3cf
19 changed files with 232 additions and 386 deletions

View File

@@ -1,39 +1,22 @@
<script>
export let onClick = () => {};
export let border = false;
export let textAlign = "left";
export let whiteSpace = "nowrap";
export let borderRadius = "8px";
export let buttonHoverStyle = "";
let isHovered = false;
</script>
<button
onclick={onClick}
class="btn"
class:borderless={!border}
style={`text-align: ${textAlign}; white-space: ${whiteSpace}; border-radius: ${borderRadius};`}
{...$$restProps}
<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 : null}
>
<slot />
</button>
<style>
.btn {
background: var(--white);
color: var(--black);
border: 1.5px solid transparent;
padding: 0.6em 1.2em;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5em;
}
.btn:not(.borderless) {
border-color: var(--orange);
}
.btn:hover {
background: var(--orange);
transition: background-color 0.2s ease, color 0.2s ease;
}
</style>

View File

@@ -2,8 +2,9 @@
import Button from "./Button.svelte";
export let name = "";
export let border = false;
export let borderRadius = "8px";
export let buttonClass = "";
export let panelClassName = "";
export let buttonHoverStyle = "";
let isOpen = false;
@@ -21,7 +22,7 @@
</script>
<div class="relative inline-block" on:focusout={handleFocusOut}>
<Button borderRadius={borderRadius} onClick={toggle} border={border}>
<Button onClick={toggle} class={buttonClass} {buttonHoverStyle}>
{name}
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -41,10 +42,11 @@
</Button>
<div
class="absolute mt-2 rounded-md shadow-lg bg-white ring-1 ring-black/5 overflow-hidden flex flex-col"
style="border-radius: {borderRadius}"
class="{panelClassName} absolute mt-2 rounded-md shadow-lg overflow-visible flex flex-col"
style:visibility={isOpen ? "visible" : "hidden"}
>
<slot />
<div>
<slot />
</div>
</div>
</div>

View File

@@ -1,146 +0,0 @@
<script>
export let title = "";
export let description = "";
export let icon = null;
export let href = "";
export let variant = "default"; // 'default' | 'elevated' | 'bordered' | 'glass'
export let iconColor = "var(--orange)";
export let className = "";
const variantClasses = {
default: "card-default",
elevated: "card-elevated",
bordered: "card-bordered",
glass: "card-glass",
};
</script>
{#if href}
<a {href} class={`feature-card ${variantClasses[variant]} ${className}`}>
{#if icon}
<div class="feature-icon" style="color: {iconColor}">
<svelte:component this={icon} size={32} strokeWidth={1.5} />
</div>
{/if}
{#if title}
<h3 class="feature-title">{title}</h3>
{/if}
{#if description}
<p class="feature-description">{description}</p>
{/if}
<div class="feature-content">
<slot />
</div>
</a>
{:else}
<div class={`feature-card ${variantClasses[variant]} ${className}`}>
{#if icon}
<div class="feature-icon" style="color: {iconColor}">
<svelte:component this={icon} size={32} strokeWidth={1.5} />
</div>
{/if}
{#if title}
<h3 class="feature-title">{title}</h3>
{/if}
{#if description}
<p class="feature-description">{description}</p>
{/if}
<div class="feature-content">
<slot />
</div>
</div>
{/if}
<style>
.feature-card {
position: relative;
padding: var(--space-5);
border-radius: var(--radius-lg);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: block;
text-decoration: none;
color: inherit;
}
.feature-card:hover {
transform: translateY(-4px);
}
a.feature-card:hover {
text-decoration: none;
}
.card-default {
background: var(--white);
border: 1px solid var(--gray-200);
}
.card-default:hover {
box-shadow: var(--shadow-2);
border-color: var(--orange);
}
.card-elevated {
background: var(--white);
box-shadow: var(--shadow-2);
border: 1px solid transparent;
}
.card-elevated:hover {
box-shadow: var(--shadow-3);
border-color: var(--orange);
}
.card-bordered {
background: transparent;
border: 2px solid var(--gray-200);
}
.card-bordered:hover {
border-color: var(--orange);
background: var(--gray-50);
}
.card-glass {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.card-glass:hover {
background: rgba(255, 255, 255, 0.9);
box-shadow: var(--shadow-glow);
}
.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);
}
.feature-content {
margin-top: var(--space-3);
}
@media (max-width: 768px) {
.feature-card {
padding: var(--space-4);
}
.feature-title {
font-size: var(--fs-lg);
}
}
</style>

View File

@@ -4,8 +4,6 @@ export { default as Stack } from './layout/Stack.svelte'
export { default as Grid } from './layout/Grid.svelte'
export { default as Center } from './layout/Center.svelte'
export { default as Section } from './layout/Section.svelte'
export { default as FullWidthBanner } from './layout/FullWidthBanner.svelte'
export { default as Button } from './Button.svelte'
export { default as Dropdown } from './Dropdown.svelte'
export { default as FeatureCard } from './FeatureCard.svelte'

View File

@@ -1,12 +1,88 @@
<!--
A simple presentational container that renders a `div` with the `card` class.
-->
<script>
export let href = "";
export let variant = "default"; // 'default' | 'animated' | 'animated-neutral' | 'glass'
export let className = "";
export let background = "var(--off-white)";
const variantClasses = {
default: "card-default",
animated: "card-animated",
"animated-neutral": "card-animated-neutral",
glass: "card-glass",
};
</script>
<div class={`card ${className}`} style="background: {background}">
<slot />
</div>
{#if href}
<a {href} class={`card ${variantClasses[variant]} ${className}`}>
<slot />
</a>
{:else}
<div class={`card ${variantClasses[variant]} ${className}`}>
<slot />
</div>
{/if}
<style>
.card {
position: relative;
padding: var(--space-5);
border-radius: var(--radius-lg);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: block;
text-decoration: none;
color: inherit;
}
.card:hover {
transform: translateY(-4px);
}
a.card:hover {
text-decoration: none;
}
.card-default {
background: var(--white);
border: 2px solid var(--gray-200);
}
.card-default:hover {
transform: none;
}
.card-animated {
background: transparent;
border: 2px solid var(--gray-200);
}
.card-animated:hover {
border-color: var(--orange);
background: var(--gray-50);
}
.card-animated-neutral {
background: transparent;
border: 2px solid var(--gray-200);
}
.card-animated-neutral:hover {
background: var(--gray-50);
}
.card-glass {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.12);
}
.card-glass:hover {
transform: none;
}
@media (max-width: 768px) {
.card {
padding: var(--space-4);
}
}
</style>

View File

@@ -7,9 +7,10 @@ projected via the default slot.
<script>
export let maxWidth = false; // true removes max-width
export let center = true; // false prevents centering and removes horizontal padding
export let className = "";
</script>
<div class={`container ${maxWidth ? "container--fluid" : ""} ${className}`}>
<div class="w-full {center ? 'px-4 md:px-6 lg:px-8' : ''} {maxWidth ? '' : 'max-w-[1200px]'} {center ? 'mx-auto' : ''} {className}">
<slot />
</div>

View File

@@ -1,18 +0,0 @@
<!--
Full-width colored section with configurable background and padding.
Content is projected via the default slot.
-->
<script>
export let background = "var(--orange)";
export let paddingX = "var(--site-padding)";
export let paddingY = "var(--space-4)";
export let className = "";
</script>
<div
class="w-screen relative left-1/2 right-1/2 -ml-[50vw] -mr-[50vw] flex justify-center {className}"
style="background: {background}; padding: {paddingY} {paddingX};"
>
<slot />
</div>

View File

@@ -11,8 +11,8 @@ spacing. Content is projected into the default slot.
</script>
<div
class={`grid ${className}`}
style={`grid-template-columns: repeat(auto-fit, minmax(${min}, 1fr)); gap: ${gap};`}
class="grid {className}"
style="grid-template-columns: repeat(auto-fit, minmax({min}, 1fr)); gap: {gap};"
>
<slot />
</div>

View File

@@ -7,9 +7,9 @@
const paddingClasses = {
none: "py-0",
small: "py-section-sm",
normal: "py-section",
large: "py-section-lg",
small: "py-8",
normal: "py-12 max-md:py-8",
large: "py-[clamp(3rem,8vw,6rem)] max-md:py-12",
};
// Convert background to appropriate CSS value
@@ -26,59 +26,10 @@
<section
{id}
class={`section ${paddingClasses[padding]} ${className}`}
class="w-full relative {paddingClasses[padding]} {className}"
style={backgroundStyle ? `background: ${backgroundStyle};` : ""}
>
<div class="section-inner" class:full-width={fullWidth}>
<div class="{fullWidth ? '' : 'max-w-[1200px]'} mx-auto px-(--site-padding)">
<slot />
</div>
</section>
<style>
.section {
width: 100%;
position: relative;
}
.section-inner {
max-width: var(--content-max);
margin: 0 auto;
padding: 0 var(--site-padding);
}
.section-inner.full-width {
max-width: none;
}
.py-0 {
padding-top: 0;
padding-bottom: 0;
}
.py-section-sm {
padding-top: var(--space-5);
padding-bottom: var(--space-5);
}
.py-section {
padding-top: var(--space-6);
padding-bottom: var(--space-6);
}
.py-section-lg {
padding-top: clamp(3rem, 8vw, 6rem);
padding-bottom: clamp(3rem, 8vw, 6rem);
}
@media (max-width: 768px) {
.py-section {
padding-top: var(--space-5);
padding-bottom: var(--space-5);
}
.py-section-lg {
padding-top: var(--space-6);
padding-bottom: var(--space-6);
}
}
</style>

View File

@@ -8,6 +8,6 @@ A simple stack/column layout that stacks children vertically.
export let className = "";
</script>
<div class={`stack ${className}`} style={`gap: ${gap}; align-items: ${align};`}>
<div class="flex flex-col {className}" style="gap: {gap}; align-items: {align};">
<slot />
</div>