add:badges

This commit is contained in:
kokopi-dev
2026-03-10 19:06:17 +09:00
parent 63699c2450
commit 313548cd99
9 changed files with 225 additions and 127 deletions

4
README.md Normal file
View File

@@ -0,0 +1,4 @@
# Personal Site
Ideas:
- dynamic metadata insertion, for ogcards

47
components/badge.templ Normal file
View File

@@ -0,0 +1,47 @@
package components
type BadgeVariant string
const (
BadgeInProgress BadgeVariant = "in-progress"
BadgeDeployed BadgeVariant = "deployed"
)
type badgeConfig struct {
label string
dot string
class string
}
func badgeProps(variant BadgeVariant) badgeConfig {
switch variant {
case BadgeDeployed:
return badgeConfig{
label: "Deployed",
dot: "bg-teal-500",
class: "bg-teal-100 text-teal-700 ring-teal-600/20",
}
default: // in-progress
return badgeConfig{
label: "In Progress",
dot: "bg-violet-400",
class: "bg-violet-100 text-violet-700 ring-violet-600/20",
}
}
}
templ Badge(variant BadgeVariant) {
{{ props := badgeProps(variant) }}
<span
class={ "flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ring-1 ring-inset" , props.class }
>
if props.label == "Deployed" {
<span class="size-3.5 text-teal-600">
<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="0"><animate id="spinner_kIfO" begin="0;spinner_xBIM.end" attributeName="r" calcMode="spline" dur="1.2s" values="0;11" keySplines=".52,.6,.25,.99" fill="freeze"/><animate begin="0;spinner_xBIM.end" attributeName="opacity" calcMode="spline" dur="1.2s" values="1;0" keySplines=".52,.6,.25,.99" fill="freeze"/></circle><circle cx="12" cy="12" r="0"><animate id="spinner_Pbsh" begin="spinner_kIfO.begin+0.2s" attributeName="r" calcMode="spline" dur="1.2s" values="0;11" keySplines=".52,.6,.25,.99" fill="freeze"/><animate begin="spinner_kIfO.begin+0.2s" attributeName="opacity" calcMode="spline" dur="1.2s" values="1;0" keySplines=".52,.6,.25,.99" fill="freeze"/></circle><circle cx="12" cy="12" r="0"><animate id="spinner_xBIM" begin="spinner_kIfO.begin+0.4s" attributeName="r" calcMode="spline" dur="1.2s" values="0;11" keySplines=".52,.6,.25,.99" fill="freeze"/><animate begin="spinner_kIfO.begin+0.4s" attributeName="opacity" calcMode="spline" dur="1.2s" values="1;0" keySplines=".52,.6,.25,.99" fill="freeze"/></circle></svg>
</span>
} else {
<span class={ "h-1.5 w-1.5 rounded-full animate-pulse" , props.dot }></span>
}
{ props.label }
</span>
}

View File

@@ -1,5 +1,21 @@
package icons
templ Ban(className string) {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class={ className }
>
<circle cx="12" cy="12" r="10"></circle>
<path d="M4.929 4.929 19.07 19.071"></path>
</svg>
}
templ ArrowUpRight(className string) {
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -4,43 +4,8 @@ import (
"fmt"
"personal-site/constants"
"time"
"personal-site/components/icons"
)
templ navbar(params *constants.LayoutParams) {
<header class="sticky top-0 z-50 w-full border-b border-zinc-800 bg-bg-100/80 backdrop-blur-sm">
<div class="max-w-3xl mx-auto px-4 h-12 flex items-center justify-between">
<a
href="/"
class="font-mono text-xs text-fg-200 tracking-widest uppercase hover:text-fg-100 transition-colors duration-150 shrink-0"
>
{ params.NavTitle }
</a>
<nav class="flex items-center gap-4">
<a
href="/projects"
class="text-xs text-fg-300 hover:text-fg-100 transition-colors duration-150"
>projects</a>
<a
href="https://git.kokopi.dev/kokopi/personal-site"
class="text-xs text-fg-300 hover:text-fg-100 transition-colors duration-150 flex items-center gap-1.5 sm:p-0 p-0.5"
aria-label="Gitea"
>
@icons.Gitea("size-4 shrink-0")
<span class="hidden sm:inline">gitea</span>
</a>
<a
href="https://github.com/kokopi-dev/personal-site"
class="text-xs text-fg-300 hover:text-fg-100 transition-colors duration-150 flex items-center gap-1.5 sm:p-0 p-0.5"
aria-label="GitHub"
>
@icons.Github("size-4 shrink-0")
<span class="hidden sm:inline">github</span>
</a>
</nav>
</div>
</header>
}
templ MainLayout(params *constants.LayoutParams) {
@@ -53,7 +18,7 @@ templ MainLayout(params *constants.LayoutParams) {
</head>
<body class="relative bg-linear-to-b from-bg-400 to-bg-100 text-fg-100 overflow-hidden font-sans">
<div class="h-screen grid grid-rows-[auto_1fr_auto] overflow-y-auto overflow-x-hidden">
@navbar(params)
@Navbar(params)
// base container
<div class="p-4 mt-6 flex flex-col gap-6 max-w-3xl mx-auto">
{ children... }

39
components/navbar.templ Normal file
View File

@@ -0,0 +1,39 @@
package components
import "personal-site/constants"
import "personal-site/components/icons"
templ Navbar(params *constants.LayoutParams) {
<header class="sticky top-0 z-50 w-full border-b border-zinc-800 bg-bg-100/80 backdrop-blur-sm">
<div class="max-w-3xl mx-auto px-4 h-12 flex items-center justify-between">
<a
href="/"
class="font-mono text-xs text-fg-200 tracking-widest uppercase hover:text-fg-100 transition-colors duration-150 shrink-0"
>
{ params.NavTitle }
</a>
<nav class="flex items-center gap-4">
<a
href="/projects"
class="text-xs text-fg-300 hover:text-fg-100 transition-colors duration-150"
>projects</a>
<a
href="https://git.kokopi.dev/kokopi/personal-site"
class="text-xs text-fg-300 hover:text-fg-100 transition-colors duration-150 flex items-center gap-1.5 sm:p-0 p-0.5"
aria-label="Gitea"
>
@icons.Gitea("size-4 shrink-0")
<span class="hidden sm:inline">gitea</span>
</a>
<a
href="https://github.com/kokopi-dev/personal-site"
class="text-xs text-fg-300 hover:text-fg-100 transition-colors duration-150 flex items-center gap-1.5 sm:p-0 p-0.5"
aria-label="GitHub"
>
@icons.Github("size-4 shrink-0")
<span class="hidden sm:inline">github</span>
</a>
</nav>
</div>
</header>
}

View File

@@ -8,4 +8,38 @@ type Project struct {
LinkGitea string
LinkGithub string
HostedOn string
IsWIP bool
}
var AllProjects = []Project{
{
Url: "https://derrickgee.dev/projects/support-ticket-demo",
Name: "Support Ticket System",
Description: "A sample of a robust support ticket system with the option to OAuth for database access. Authenticated users will be able to see other user created tickets. Guest mode will store tickets locally in your browser.",
HostedOn: "Home Server",
LinkGitea: "https://git.kokopi.dev/kokopi/personal-support-ticket-system",
LinkGithub: "https://github.com/kokopi-dev/personal-support-ticket-system",
TechTags: []string{"typescript", "react", "fastify", "tailwindcss", "sqlite", "vite", "bun"},
IsWIP: false,
},
{
Url: "/projects/dotfiles",
Name: "Linux Dotfiles",
Description: "Dotfile configurations for specific linux applications.",
HostedOn: "",
LinkGitea: "https://git.kokopi.dev/kokopi/dotfiles",
LinkGithub: "https://github.com/kokopi-dev/dotfiles",
TechTags: []string{"linux", "lua", "bash", "python"},
IsWIP: false,
},
{
Url: "/projects/dotfiles",
Name: "Koko-chan",
Description: "Paid discord bot service for handling schedules.",
HostedOn: "",
LinkGitea: "",
LinkGithub: "",
TechTags: []string{"golang", "templ", "tailwindcss"},
IsWIP: true,
},
}

8
helpers/ui.go Normal file
View File

@@ -0,0 +1,8 @@
package helpers
func TemplIf[T any](cond bool, a, b T) T {
if cond {
return a
}
return b
}

23
main.go
View File

@@ -23,28 +23,9 @@ func setupRoutesAndMiddleware() *gin.Engine {
page := pages.Index()
page.Render(c.Request.Context(), c.Writer)
})
var projects = []constants.Project{
{
Url: "https://derrickgee.dev/projects/support-ticket-demo",
Name: "Support Ticket System",
Description: "A sample of a robust support ticket system with the option to OAuth for database access. Authenticated users will be able to see other user created tickets. Guest mode will store tickets locally in your browser.",
HostedOn: "Home Server",
LinkGitea: "https://git.kokopi.dev/kokopi/personal-support-ticket-system",
LinkGithub: "https://github.com/kokopi-dev/personal-support-ticket-system",
TechTags: []string{"typescript", "react", "fastify", "tailwindcss", "sqlite", "vite", "bun"},
},
{
Url: "/projects/dotfiles",
Name: "Linux Dotfiles",
Description: "Configurations for Linux",
HostedOn: "",
LinkGitea: "https://git.kokopi.dev/kokopi/dotfiles",
LinkGithub: "https://github.com/kokopi-dev/dotfiles",
TechTags: []string{"Linux", "Lua", "Bash", "Python"},
},
}
// projects defined in constants
r.GET("/projects", func(c *gin.Context) {
page := pages.ProjectPage(projects)
page := pages.ProjectPage(constants.AllProjects)
page.Render(c.Request.Context(), c.Writer)
})
r.GET("/projects/dotfiles", func(c *gin.Context) {

View File

@@ -4,16 +4,22 @@ import "personal-site/constants"
import "fmt"
import "personal-site/components"
import "personal-site/components/icons"
import "personal-site/helpers"
templ projectCard(idx int, project constants.Project) {
<div
class="group relative rounded-lg px-5 py-4 flex items-start justify-between gap-4 fade-up bg-[#111113] border border-zinc-800 transition-all duration-150 ease-in-out hover:border-zinc-600 hover:bg-[#18181b] hover:-translate-y-0.5"
style={ fmt.Sprintf("animation-delay: %dms", idx*30) }
>
<a href={ project.Url } target="_blank" class="absolute inset-0 rounded-lg" aria-label={ project.Name }></a>
<div class="pointer-events-none relative z-10 flex-1 min-w-0">
<div class="group relative rounded-lg px-5 py-4 flex items-start justify-between gap-4 fade-up bg-[#111113] border border-zinc-800 transition-all duration-150 ease-in-out hover:border-zinc-600 hover:bg-[#18181b] hover:-translate-y-0.5"
style={ fmt.Sprintf("animation-delay: %dms", idx*30) }>
<a href={ project.Url } target="_blank" class={ helpers.TemplIf(project.IsWIP, "pointer-events-none" , "" ) + " absolute inset-0 rounded-lg" } aria-label={ project.Name }></a>
<div class={ helpers.TemplIf(project.IsWIP, "text-fg-300", "") + " pointer-events-none relative z-10 flex-1 min-w-0"}>
<div class="flex items-center gap-2.5 mb-1.5">
<span class="text-sm font-medium">{ project.Name }</span>
<span>
if project.IsWIP {
@components.Badge(components.BadgeInProgress)
} else {
@components.Badge(components.BadgeDeployed)
}
</span>
</div>
<p class="text-zinc-500 text-xs leading-relaxed mb-3">{ project.Description }</p>
<div class="flex flex-wrap gap-1.5 mb-3">
@@ -29,33 +35,31 @@ templ projectCard(idx int, project constants.Project) {
if project.LinkGitea != "" || project.LinkGithub != "" {
<div class="flex items-center gap-2">
if project.LinkGitea != "" {
<a
href={ templ.URL(project.LinkGitea) }
target="_blank"
class="pointer-events-auto inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[0.6875rem] font-medium bg-zinc-800 text-fg-200 border border-zinc-700 hover:border-zinc-500 hover:text-fg-100 transition-all duration-150"
>
<a href={ templ.URL(project.LinkGitea) } target="_blank"
class={ helpers.TemplIf(project.IsWIP, "pointer-events-none", "pointer-events-auto") + " inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[0.6875rem] font-medium bg-zinc-800 text-fg-200 border border-zinc-700 hover:border-zinc-500 hover:text-fg-100 transition-all duration-150"}>
@icons.Gitea("size-4")
Gitea
@icons.ArrowUpRight("size-3")
</a>
}
if project.LinkGithub != "" {
<a
href={ templ.URL(project.LinkGithub) }
target="_blank"
class="pointer-events-auto inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[0.6875rem] font-medium bg-zinc-800 text-fg-200 border border-zinc-700 hover:border-zinc-500 hover:text-fg-100 transition-all duration-150"
>
<a href={ templ.URL(project.LinkGithub) } target="_blank"
class={ helpers.TemplIf(project.IsWIP, "pointer-events-none", "pointer-events-auto") + " inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[0.6875rem] font-medium bg-zinc-800 text-fg-200 border border-zinc-700 hover:border-zinc-500 hover:text-fg-100 transition-all duration-150"}>
@icons.Github("size-4")
GitHub
@icons.ArrowUpRight("size-3")
</a>
}
</div>
} else {
<div class="pointer-events-none inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[0.6875rem] font-medium bg-zinc-800 text-fg-200 border border-zinc-700 hover:border-zinc-500 hover:text-fg-100 transition-all duration-150">
@icons.Ban("size-4")
Closed Source
</div>
}
</div>
<div
class="arrow relative z-10 text-fg-200 mt-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-150 ease-in-out"
>
class="arrow relative z-10 text-fg-200 mt-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-150 ease-in-out">
@icons.ArrowUpRight("size-4")
</div>
</div>