diff --git a/.templui.json b/.templui.json new file mode 100644 index 0000000..b6939a8 --- /dev/null +++ b/.templui.json @@ -0,0 +1,7 @@ +{ + "componentsDir": "web/templates/components", + "utilsDir": "web/resources/utils", + "moduleName": "maot-shortner", + "jsDir": "web/resources/assets/js", + "jsPublicPath": "/web/static/js" +} diff --git a/cmd/maot-shortner/server.go b/cmd/maot-shortner/server.go index 77f4d28..923ae71 100644 --- a/cmd/maot-shortner/server.go +++ b/cmd/maot-shortner/server.go @@ -29,5 +29,8 @@ func createMux(db *database.Database) http.Handler { r.HandleFunc("POST /api/link", linkHandler.CreateLink) r.HandleFunc("GET /{id}", linkHandler.GetUrl) + // Static content + r.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static")))) + return handlers.LogMiddleware(r) } diff --git a/web/resources/css/input.css b/web/resources/css/input.css new file mode 100644 index 0000000..d91314f --- /dev/null +++ b/web/resources/css/input.css @@ -0,0 +1,142 @@ +@import "tailwindcss"; + +@custom-variant dark (&:where(.dark, .dark *)); + +@theme inline { + --breakpoint-3xl: 1600px; + --breakpoint-4xl: 2000px; + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + --color-surface: var(--surface); + --color-surface-foreground: var(--surface-foreground); + --color-code: var(--code); + --color-code-foreground: var(--code-foreground); + --color-code-highlight: var(--code-highlight); + --color-code-number: var(--code-number); + --color-selection: var(--selection); + --color-selection-foreground: var(--selection-foreground); +} + +/* Default theme - Neutral gray */ +:root { + --radius: 0.65rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); + --selection: oklch(0.145 0 0); + --selection-foreground: oklch(1 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); + --selection: oklch(0.922 0 0); + --selection-foreground: oklch(0.205 0 0); +} + +@layer base { + * { + @apply border-border; + } + ::selection { + @apply bg-selection text-selection-foreground; + } + + body { + @apply bg-background text-foreground; + font-feature-settings: + "rlig" 1, + "calt" 1; + } +} diff --git a/web/templates/pages/home.templ b/web/templates/pages/home.templ new file mode 100644 index 0000000..e69de29 diff --git a/web/templates/utils/templui.go b/web/templates/utils/templui.go new file mode 100644 index 0000000..5d00892 --- /dev/null +++ b/web/templates/utils/templui.go @@ -0,0 +1,55 @@ +// templui util templui.go - version: v0.85.0 installed by templui v0.85.0 +package utils + +import ( + "fmt" + + "crypto/rand" + + "github.com/a-h/templ" + + twmerge "github.com/Oudwins/tailwind-merge-go" +) + +// TwMerge combines Tailwind classes and resolves conflicts. +// Example: "bg-red-500 hover:bg-blue-500", "bg-green-500" → "hover:bg-blue-500 bg-green-500" +func TwMerge(classes ...string) string { + return twmerge.Merge(classes...) +} + +// TwIf returns value if condition is true, otherwise an empty value of type T. +// Example: true, "bg-red-500" → "bg-red-500" +func If[T comparable](condition bool, value T) T { + var empty T + if condition { + return value + } + return empty +} + +// TwIfElse returns trueValue if condition is true, otherwise falseValue. +// Example: true, "bg-red-500", "bg-gray-300" → "bg-red-500" +func IfElse[T any](condition bool, trueValue T, falseValue T) T { + if condition { + return trueValue + } + return falseValue +} + +// MergeAttributes combines multiple Attributes into one. +// Example: MergeAttributes(attr1, attr2) → combined attributes +func MergeAttributes(attrs ...templ.Attributes) templ.Attributes { + merged := templ.Attributes{} + for _, attr := range attrs { + for k, v := range attr { + merged[k] = v + } + } + return merged +} + +// RandomID generates a random ID string. +// Example: RandomID() → "id-1a2b3c" +func RandomID() string { + return fmt.Sprintf("id-%s", rand.Text()) +}