feat: Sochboard logo with dynamic accent color as favicon and header icon

- New SochboardLogo SVG component used in dashboard header
- Favicon generated from theme accent color
- Bump version to 0.2.0
This commit is contained in:
maxsoch 2026-08-02 17:53:10 +02:00
parent d36f16cea1
commit 4807899b38
5 changed files with 82 additions and 9 deletions

View file

@ -1,6 +1,6 @@
{
"name": "sochboard",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"scripts": {
"dev": "node node_modules/next/dist/bin/next dev",

View file

@ -1,6 +1,7 @@
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { getConfig } from "@/lib/config"
import { SochboardLogoDataUri } from "@/components/ui/SochboardLogo"
import "./globals.css"
const inter = Inter({
@ -15,12 +16,17 @@ export async function generateMetadata(): Promise<Metadata> {
return {
title: config.site.name,
description: config.site.title,
icons: config.site.favicon ? { icon: config.site.favicon } : undefined,
icons: {
icon: config.site.favicon ?? SochboardLogoDataUri(config.theme.accent),
},
}
} catch {
return {
title: "Sochboard",
description: "Dashboard",
icons: {
icon: SochboardLogoDataUri(),
},
}
}
}

View file

@ -19,6 +19,7 @@ export function Dashboard({ config, isAuthenticated = false, username = null, cu
authentication={config.authentication}
isAuthenticated={isAuthenticated}
username={username}
accent={config.theme.accent}
/>
<main className="flex flex-col gap-12">
{config.sections.map((section, index) => (

View file

@ -2,6 +2,7 @@
import { useState, useEffect } from "react"
import type { SiteConfig, AuthenticationConfig } from "@/types/config"
import { SochboardLogo } from "@/components/ui/SochboardLogo"
interface DashboardHeaderProps {
site: SiteConfig
@ -9,9 +10,10 @@ interface DashboardHeaderProps {
authentication?: AuthenticationConfig
isAuthenticated?: boolean
username?: string | null
accent?: string
}
export function DashboardHeader({ site, initialTheme, authentication, isAuthenticated = false, username = null }: DashboardHeaderProps) {
export function DashboardHeader({ site, initialTheme, authentication, isAuthenticated = false, username = null, accent }: DashboardHeaderProps) {
const [isLight, setIsLight] = useState(initialTheme === "light")
useEffect(() => {
@ -33,12 +35,7 @@ export function DashboardHeader({ site, initialTheme, authentication, isAuthenti
className="size-12 rounded-xl object-cover"
/>
) : (
<div
className="flex size-12 items-center justify-center rounded-xl bg-gradient-to-br from-[#8b5cf6] to-[#6d28d9] text-lg font-bold text-white shadow-lg shadow-[#8b5cf6]/20"
aria-hidden="true"
>
S
</div>
<SochboardLogo size={48} accent={accent} />
)}
<div>
<h1 className="text-xl font-bold text-[var(--text-primary)]">

View file

@ -0,0 +1,69 @@
interface SochboardLogoProps {
size?: number
accent?: string
className?: string
}
export function SochboardLogo({ size = 48, accent = "#f59e0b", className }: SochboardLogoProps) {
return (
<svg
width={size}
height={size}
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
aria-hidden="true"
>
<rect width="48" height="48" rx="12" fill={accent} />
<rect x="4" y="4" width="40" height="40" rx="10" fill={`${accent}20`} />
<path
d="M24 12C21.5 12 19 13.5 19 16.5C19 18 20 19 21 19.5C19.5 20 18 21.5 18 24C18 26.5 20 28 22 28C20.5 28.5 19 30 19 32.5C19 35.5 21.5 37 24 37C26.5 37 29 35.5 29 32.5C29 31 28 30 27 29.5C28.5 29 30 27.5 30 25C30 22.5 28 21 26 21C27.5 20.5 29 19 29 16.5C29 13.5 26.5 12 24 12Z"
fill="white"
fillOpacity="0.95"
/>
<path
d="M12 12H36"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeOpacity="0.2"
/>
<path
d="M12 36H36"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeOpacity="0.2"
/>
<path
d="M12 24H36"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeOpacity="0.2"
/>
<path
d="M24 12V36"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeOpacity="0.2"
/>
</svg>
)
}
export function SochboardLogoDataUri(accent: string = "#f59e0b"): string {
const hex = accent.replace("#", "%23")
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
<rect width="48" height="48" rx="12" fill="${hex}"/>
<rect x="4" y="4" width="40" height="40" rx="10" fill="${hex}20"/>
<path d="M24 12C21.5 12 19 13.5 19 16.5C19 18 20 19 21 19.5C19.5 20 18 21.5 18 24C18 26.5 20 28 22 28C20.5 28.5 19 30 19 32.5C19 35.5 21.5 37 24 37C26.5 37 29 35.5 29 32.5C29 31 28 30 27 29.5C28.5 29 30 27.5 30 25C30 22.5 28 21 26 21C27.5 20.5 29 19 29 16.5C29 13.5 26.5 12 24 12Z" fill="white" fill-opacity="0.95"/>
<path d="M12 12H36" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-opacity="0.2"/>
<path d="M12 36H36" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-opacity="0.2"/>
<path d="M12 24H36" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-opacity="0.2"/>
<path d="M24 12V36" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-opacity="0.2"/>
</svg>`
return `data:image/svg+xml,${encodeURIComponent(svg)}`
}