Website Redesign 7

This commit is contained in:
2025-11-28 02:40:12 +00:00
parent 9b9a201c3e
commit 96e2d0650c
72 changed files with 7504 additions and 1231 deletions

View File

@@ -0,0 +1,504 @@
<script lang="ts">
import { mode, colorTheme, toggleMode, setColorTheme, themeOptions, themeColors, type ColorTheme } from '$lib/stores/theme';
import { page } from '$app/stores';
import { fly, fade } from 'svelte/transition';
import { user, navigation, colorPalette } from '$lib/config';
import Icon from '@iconify/svelte';
import { onMount, onDestroy } from 'svelte';
// State
let themeDropdownOpen = $state(false);
let currentTime = $state(new Date());
// Derived values (Svelte 5 runes)
let currentPath = $derived($page.url.pathname);
// Time update interval
let timeInterval: ReturnType<typeof setInterval>;
onMount(() => {
// Update time every second
timeInterval = setInterval(() => {
currentTime = new Date();
}, 1000);
});
onDestroy(() => {
if (timeInterval) clearInterval(timeInterval);
});
// Format time
function formatTime(date: Date): string {
return date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
}
function formatDate(date: Date): string {
return date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
});
}
// Get current workspace/page index
function getWorkspaceIndex(path: string): number {
const idx = navigation.findIndex(n => n.path === path);
return idx >= 0 ? idx + 1 : 1;
}
// Battery icon based on level
function getBatteryIcon(level: number, charging: boolean): string {
if (charging) return 'mdi:battery-charging';
if (level > 90) return 'mdi:battery';
if (level > 70) return 'mdi:battery-80';
if (level > 50) return 'mdi:battery-60';
if (level > 30) return 'mdi:battery-40';
if (level > 10) return 'mdi:battery-20';
return 'mdi:battery-alert';
}
function handleThemeSelect(theme: ColorTheme) {
setColorTheme(theme);
themeDropdownOpen = false;
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
themeDropdownOpen = false;
}
}
function getThemeIcon(theme: ColorTheme): string {
switch (theme) {
case 'arch': return 'mdi:arch';
case 'catppuccin': return 'solar:cat-bold';
default: return 'mdi:palette';
}
}
</script>
<svelte:window on:keydown={handleKeydown} />
<nav
class="waybar"
style="
--bar-bg: {$themeColors.background};
--bar-bg-module: {$themeColors.backgroundLight};
--bar-border: {$themeColors.border};
--bar-text: {$themeColors.text};
--bar-primary: {$themeColors.primary};
--bar-accent: {$themeColors.accent};
--bar-muted: {$themeColors.textMuted};
--bar-success: {colorPalette.success};
--bar-warning: {colorPalette.warning};
--bar-error: {colorPalette.error};
"
>
<!-- Left modules -->
<div class="bar-left">
<!-- Arch logo / Launcher -->
<a href="/" class="module launcher" title="Home">
<img src="/favicon.png" alt="Blob Icon" width="16" />
</a>
<!-- Workspaces -->
<div class="module workspaces">
{#each navigation as nav}
{@const isActive = currentPath === nav.path || (nav.path !== '/' && currentPath.startsWith(nav.path))}
{#if nav.external}
<a
href={nav.path}
class="workspace external"
target="_blank"
rel="noopener noreferrer"
title={nav.name}
>
<Icon icon="mdi:open-in-new" width="12" />
<span class="ws-name">{nav.name}</span>
</a>
{:else}
<a
href={nav.path}
class="workspace"
class:active={isActive}
title={nav.name}
>
<span class="ws-name">{nav.name}</span>
</a>
{/if}
{/each}
</div>
<!-- Current window title -->
<div class="module window-title">
<span class="title-icon">
{#if currentPath === '/'}
<Icon icon="mdi:home" width="14" />
{:else if currentPath === '/portfolio'}
<Icon icon="mdi:folder-multiple" width="14" />
{:else if currentPath === '/models'}
<Icon icon="mdi:cube-outline" width="14" />
{:else if currentPath === '/hackathons'}
<Icon icon="mdi:trophy" width="14" />
{:else}
<Icon icon="mdi:file" width="14" />
{/if}
</span>
<span class="title-text">
{navigation.find(n => n.path === currentPath)?.name || 'page'}
</span>
</div>
</div>
<!-- Center modules -->
<div class="bar-center">
<div class="module clock">
<Icon icon="mdi:clock-outline" width="14" />
<span class="time">{formatTime(currentTime)}</span>
<span class="date">{formatDate(currentTime)}</span>
</div>
</div>
<!-- Right modules -->
<div class="bar-right">
<!-- (No system modules — minimal Waybar look) -->
<!-- Theme selector -->
<div class="module theme-selector">
<button
class="theme-trigger"
onclick={() => themeDropdownOpen = !themeDropdownOpen}
aria-expanded={themeDropdownOpen}
title="Theme: {$colorTheme}"
>
<Icon icon={getThemeIcon($colorTheme)} width="16" />
</button>
{#if themeDropdownOpen}
<div
class="theme-dropdown"
transition:fly={{ y: -10, duration: 150 }}
>
<div class="dropdown-header">Theme</div>
{#each themeOptions as option}
<button
class="theme-option"
class:active={$colorTheme === option.value}
onclick={() => handleThemeSelect(option.value)}
>
<Icon icon={getThemeIcon(option.value)} width="16" />
<span>{option.label}</span>
{#if $colorTheme === option.value}
<Icon icon="mdi:check" width="14" class="check" />
{/if}
</button>
{/each}
<div class="dropdown-divider"></div>
<div class="dropdown-header">Mode</div>
<button class="theme-option" onclick={toggleMode}>
<Icon icon={$mode === 'dark' ? 'mdi:weather-sunny' : 'mdi:weather-night'} width="16" />
<span>{$mode === 'dark' ? 'Light Mode' : 'Dark Mode'}</span>
</button>
</div>
{/if}
</div>
</div>
<!-- Backdrop -->
{#if themeDropdownOpen}
<button
class="backdrop"
transition:fade={{ duration: 100 }}
onclick={() => themeDropdownOpen = false}
aria-label="Close"
></button>
{/if}
</nav>
<style>
.waybar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
height: var(--navbar-height, 40px);
background: var(--bar-bg);
border-bottom: 1px solid var(--bar-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0.5rem;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.8rem;
gap: 0.5rem;
}
.bar-left,
.bar-center,
.bar-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.bar-left {
flex: 1;
}
.bar-right {
flex: 1;
justify-content: flex-end;
}
/* Modules */
.module {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.35rem 0.6rem;
background: var(--bar-bg-module);
border-radius: 4px;
color: var(--bar-text);
transition: all 0.15s ease;
}
.module:hover {
background: color-mix(in srgb, var(--bar-primary) 20%, var(--bar-bg-module));
}
/* module-group removed (minimal waybar look uses individual modules) */
/* Launcher */
.launcher {
color: var(--bar-primary);
padding: 0.35rem 0.5rem;
}
.launcher:hover {
color: var(--bar-accent);
}
/* Workspaces */
.workspaces {
display: flex;
gap: 0.25rem;
padding: 0.2rem 0.3rem;
}
.workspace {
display: flex;
align-items: center;
justify-content: center;
gap: 0.3rem;
padding: 0.3rem 0.6rem;
background: transparent;
border-radius: 4px;
color: var(--bar-muted);
font-size: 0.75rem;
font-weight: 500;
transition: all 0.15s ease;
text-decoration: none;
white-space: nowrap;
}
.workspace:hover {
background: color-mix(in srgb, var(--bar-primary) 25%, transparent);
color: var(--bar-text);
}
.workspace.active {
background: var(--bar-primary);
color: var(--bar-bg);
font-weight: 600;
}
.workspace .ws-name {
font-size: 0.75rem;
}
.workspace.external {
color: var(--bar-muted);
font-size: 0.7rem;
}
.workspace.external:hover {
color: var(--bar-accent);
}
/* Window title */
.window-title {
color: var(--bar-muted);
font-size: 0.75rem;
max-width: 200px;
overflow: hidden;
}
.title-icon {
display: flex;
color: var(--bar-primary);
}
.title-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Clock */
.clock {
background: var(--bar-bg-module);
padding: 0.35rem 0.75rem;
}
.clock .time {
font-weight: 600;
color: var(--bar-text);
}
.clock .date {
color: var(--bar-muted);
font-size: 0.7rem;
margin-left: 0.5rem;
}
/* System modules removed */
/* Theme selector */
.theme-selector {
position: relative;
padding: 0;
background: transparent;
}
.theme-trigger {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background: var(--bar-bg-module);
border: none;
border-radius: 4px;
color: var(--bar-text);
cursor: pointer;
transition: all 0.15s ease;
margin: 5px;
}
.theme-trigger:hover {
background: color-mix(in srgb, var(--bar-primary) 25%, var(--bar-bg-module));
color: var(--bar-primary);
}
.theme-dropdown {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
min-width: 160px;
background: var(--bar-bg);
border: 1px solid var(--bar-border);
border-radius: 8px;
padding: 0.4rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 1001;
}
.dropdown-header {
padding: 0.4rem 0.6rem;
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--bar-muted);
}
.dropdown-divider {
height: 1px;
background: var(--bar-border);
margin: 0.3rem 0;
}
.theme-option {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
padding: 0.5rem 0.6rem;
background: transparent;
border: none;
border-radius: 4px;
color: var(--bar-text);
font-family: inherit;
font-size: 0.75rem;
text-align: left;
cursor: pointer;
transition: all 0.15s ease;
}
.theme-option:hover {
background: color-mix(in srgb, var(--bar-primary) 15%, transparent);
}
.theme-option.active {
color: var(--bar-primary);
}
.theme-option span {
flex: 1;
}
:global(.check) {
color: var(--bar-primary);
}
/* Backdrop */
.backdrop {
position: fixed;
inset: 0;
background: transparent;
z-index: 999;
border: none;
cursor: default;
}
/* Responsive */
@media (max-width: 768px) {
.waybar {
padding: 0 0.25rem;
}
.window-title {
display: none;
}
.clock .date {
display: none;
}
}
@media (max-width: 480px) {
.workspaces {
gap: 0.15rem;
}
.workspace {
padding: 0.25rem 0.4rem;
font-size: 0.65rem;
}
.workspace .ws-name {
font-size: 0.65rem;
}
}
</style>