diff --git a/frontend/components/BroadcastBanner.tsx b/frontend/components/BroadcastBanner.tsx
index 0d83de1..264d4ec 100644
--- a/frontend/components/BroadcastBanner.tsx
+++ b/frontend/components/BroadcastBanner.tsx
@@ -1,7 +1,7 @@
"use client";
import { useCallback, useEffect, useState } from "react";
-import { AlertTriangle, Info, Sparkles, RefreshCw, Wrench, Megaphone, X, ArrowRight } from "lucide-react";
+import { X, ArrowRight } from "lucide-react";
import { fetchActiveBroadcasts, dismissBroadcast } from "@/lib/api";
import { useWebSocket } from "@/lib/event";
import { Broadcast } from "@/lib/types";
@@ -10,88 +10,46 @@ import { cn } from "@/lib/utils";
const TYPE_STYLES: Record<
string,
{
- icon: React.ElementType;
- accentBar: string;
- bg: string;
border: string;
- iconBg: string;
- iconColor: string;
- titleColor: string;
textColor: string;
button: string;
dismissHover: string;
}
> = {
info: {
- icon: Info,
- accentBar: "bg-blue-500",
- bg: "bg-blue-500/[0.04]",
- border: "border-blue-500/15",
- iconBg: "bg-blue-500/10",
- iconColor: "text-blue-400",
- titleColor: "text-blue-100",
- textColor: "text-blue-200/60",
- button: "bg-blue-500/15 text-blue-300 hover:bg-blue-500/25 border-blue-500/20",
+ border: "border-l-blue-500",
+ textColor: "text-blue-100/70",
+ button: "bg-blue-500/15 text-blue-300 hover:bg-blue-500/25",
dismissHover: "hover:bg-blue-500/10 hover:text-blue-300",
},
warning: {
- icon: AlertTriangle,
- accentBar: "bg-amber-500",
- bg: "bg-amber-500/[0.04]",
- border: "border-amber-500/15",
- iconBg: "bg-amber-500/10",
- iconColor: "text-amber-400",
- titleColor: "text-amber-100",
- textColor: "text-amber-200/60",
- button: "bg-amber-500/15 text-amber-300 hover:bg-amber-500/25 border-amber-500/20",
+ border: "border-l-amber-500",
+ textColor: "text-amber-100/70",
+ button: "bg-amber-500/15 text-amber-300 hover:bg-amber-500/25",
dismissHover: "hover:bg-amber-500/10 hover:text-amber-300",
},
update: {
- icon: RefreshCw,
- accentBar: "bg-emerald-500",
- bg: "bg-emerald-500/[0.04]",
- border: "border-emerald-500/15",
- iconBg: "bg-emerald-500/10",
- iconColor: "text-emerald-400",
- titleColor: "text-emerald-100",
- textColor: "text-emerald-200/60",
- button: "bg-emerald-500/15 text-emerald-300 hover:bg-emerald-500/25 border-emerald-500/20",
+ border: "border-l-emerald-500",
+ textColor: "text-emerald-100/70",
+ button: "bg-emerald-500/15 text-emerald-300 hover:bg-emerald-500/25",
dismissHover: "hover:bg-emerald-500/10 hover:text-emerald-300",
},
new_feature: {
- icon: Sparkles,
- accentBar: "bg-purple-500",
- bg: "bg-purple-500/[0.04]",
- border: "border-purple-500/15",
- iconBg: "bg-purple-500/10",
- iconColor: "text-purple-400",
- titleColor: "text-purple-100",
- textColor: "text-purple-200/60",
- button: "bg-purple-500/15 text-purple-300 hover:bg-purple-500/25 border-purple-500/20",
+ border: "border-l-purple-500",
+ textColor: "text-purple-100/70",
+ button: "bg-purple-500/15 text-purple-300 hover:bg-purple-500/25",
dismissHover: "hover:bg-purple-500/10 hover:text-purple-300",
},
maintenance: {
- icon: Wrench,
- accentBar: "bg-red-500",
- bg: "bg-red-500/[0.04]",
- border: "border-red-500/15",
- iconBg: "bg-red-500/10",
- iconColor: "text-red-400",
- titleColor: "text-red-100",
- textColor: "text-red-200/60",
- button: "bg-red-500/15 text-red-300 hover:bg-red-500/25 border-red-500/20",
+ border: "border-l-red-500",
+ textColor: "text-red-100/70",
+ button: "bg-red-500/15 text-red-300 hover:bg-red-500/25",
dismissHover: "hover:bg-red-500/10 hover:text-red-300",
},
announcement: {
- icon: Megaphone,
- accentBar: "bg-sky-500",
- bg: "bg-sky-500/[0.04]",
- border: "border-sky-500/15",
- iconBg: "bg-sky-500/10",
- iconColor: "text-sky-400",
- titleColor: "text-sky-100",
- textColor: "text-sky-200/60",
- button: "bg-sky-500/15 text-sky-300 hover:bg-sky-500/25 border-sky-500/20",
+ border: "border-l-sky-500",
+ textColor: "text-sky-100/70",
+ button: "bg-sky-500/15 text-sky-300 hover:bg-sky-500/25",
dismissHover: "hover:bg-sky-500/10 hover:text-sky-300",
},
};
@@ -159,30 +117,22 @@ export default function BroadcastBanner() {
{visible.map((broadcast) => {
const style = TYPE_STYLES[broadcast.type] || TYPE_STYLES.info;
- const Icon = style.icon;
const hasMessage = broadcast.message && broadcast.message.trim().length > 0;
return (
-
-
-
-
-
-
-
+
-
+
{broadcast.title}
{hasMessage && (
@@ -190,24 +140,12 @@ export default function BroadcastBanner() {
{broadcast.message}
)}
- {broadcast.action_label && broadcast.action_url && (
-
- {broadcast.action_label}
-
-
- )}
handleDismiss(broadcast.id)}
className={cn(
- "mt-1 flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground/50 transition-colors",
+ "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/50 transition-colors",
style.dismissHover,
)}
aria-label="Dismiss"
@@ -215,6 +153,19 @@ export default function BroadcastBanner() {
+
+ {broadcast.action_label && broadcast.action_url && (
+
+ {broadcast.action_label}
+
+
+ )}
);
diff --git a/frontend/components/GoogleLinkBanner.tsx b/frontend/components/GoogleLinkBanner.tsx
index 6e0c424..c021979 100644
--- a/frontend/components/GoogleLinkBanner.tsx
+++ b/frontend/components/GoogleLinkBanner.tsx
@@ -1,8 +1,7 @@
"use client";
import { useEffect, useState } from "react";
-import { AlertTriangle } from "@untitledui/icons";
-import { Chrome, X } from "lucide-react";
+import { X } from "lucide-react";
import { cn } from "@/lib/utils";
import { fetchUser } from "@/lib/api";
import { User } from "@/lib/types";
@@ -32,45 +31,39 @@ export default function GoogleLinkBanner() {
return (
-
-
-
-
-
+
-
+
Secure your account
-
+
Link your Google account for seamless sign-in and automatic profile syncing across all your devices.
-
-
- Link Google
-
+
+
+ Link Google
+
);
diff --git a/internal/email/email.go b/internal/email/email.go
index ad295e7..b2748ae 100644
--- a/internal/email/email.go
+++ b/internal/email/email.go
@@ -26,10 +26,10 @@ const (
MutedText = "#4B5563" // muted paragraph text
ButtonGold = "#D4AF37" // CTA background
ButtonGoldDark = "#B8941F" // CTA bottom border (3D effect)
- PurplePrimary = "#53389E" // brand purple (numbers, titles)
- PurpleMid = "#7F56D9" // brand purple (accents, badges)
- PurpleLight = "#F3E8FF" // light purple (icon badge bg)
- PurpleDark = "#9E77ED" // brand purple (lighter accent)
+ PurplePrimary = "#53389E" // brand purple (numbers, titles) — legacy, prefer gold
+ PurpleMid = "#7F56D9" // brand purple (accents, badges) — legacy, prefer gold
+ PurpleLight = "#F3E8FF" // light purple tint
+ PurpleDark = "#9E77ED" // brand purple (lighter accent) — legacy, prefer gold
)
// LockIconDataURI is an inline SVG padlock (Lucide-style stroke) encoded as a
@@ -309,7 +309,7 @@ func bestPracticesBody() string {
-
+
@@ -335,11 +335,9 @@ func bestPracticesBody() string {
-
-
Discover Best Practices
-
+
See how top developers solve real problems. Browse community solutions, get AI-powered code analysis, and learn from the best.
@@ -351,26 +349,26 @@ See how top developers solve real problems. Browse community solutions, get AI-p
-
+
-{{.SolutionCount}}
+{{.SolutionCount}}
Solutions
-{{.DeveloperCount}}
+{{.DeveloperCount}}
Developers
-{{.TotalLikes}}
+{{.TotalLikes}}
Likes
-{{.GoCount}}
+{{.GoCount}}
Go
-{{.PythonCount}}
+{{.PythonCount}}
Python
@@ -381,91 +379,62 @@ See how top developers solve real problems. Browse community solutions, get AI-p
-
+
-
-
-
-
-
-
-Community Solutions
+Community Solutions
Compare how others approached the same problem. Sort by most liked, fastest runtime, or newest submissions. Every solution includes the full source code and developer stats.
-
-
-
-
+
-
-
-
-
-
-
-AI-Powered Code Analysis
+AI-Powered Code Analysis
Click any solution to get instant AI analysis: quality scores, efficiency and readability ratings, time and space complexity breakdown, key techniques, strengths, and areas for improvement. Ask follow-up questions about the approach.
-
-
-
-
+
-
-
-
-
-
-
-How to Get Featured
+How to Get Featured
Solve any problem to submit your solution. Other developers can like your code, and the top-rated solutions appear at the top of Best Practices. The more problems you solve, the more your solutions get discovered.
-
-
-
{{if .TopSolutions}}
-Top Rated Solutions
+Top Rated Solutions
{{range $i, $s := .TopSolutions}}
-
+
{{.ProblemTitle}}
-by {{.UserName}} · {{.Language}}
+by {{.UserName}} · {{.Language}}
-
-
{{.Likes}}
-
+♥ {{.Likes}}
@@ -495,21 +464,21 @@ See how top developers solve real problems. Browse community solutions, get AI-p
-
-
+
-{{.PlatformName}}
+{{.PlatformName}}
{{.Tagline}}
Sent by Jerry Koko from Koder
-
+
© {{.Year}} {{.PlatformName}}. All rights reserved.
@@ -525,7 +494,7 @@ func problemReminderBody() string {
-
+
@@ -564,10 +533,10 @@ Sharpen your skills with this short exercise:
-
+
-{{.ProblemTitle}}
+{{.ProblemTitle}}
{{.ProblemExcerptHTML}}
Open Problem
@@ -582,21 +551,21 @@ Sharpen your skills with this short exercise:
-
-
+
-{{.PlatformName}}
+{{.PlatformName}}
{{.Tagline}}
Sent by Jerry Koko from Koder
-
+
© {{.Year}} {{.PlatformName}}. All rights reserved.
@@ -653,7 +622,7 @@ const passwordResetBody = `{{define "content"}}
-
+
@@ -739,9 +708,9 @@ This secure link expires in {{.Expire
-
@@ -749,12 +718,12 @@ This secure link expires in {{.Expire
-
+
-{{.PlatformName}}
+{{.PlatformName}}
{{.Tagline}}
Sent by Jerry Koko from Koder
-
+
© {{.Year}} {{.PlatformName}}. All rights reserved.
diff --git a/internal/email/email_test.go b/internal/email/email_test.go
index a2720e5..dc4c234 100644
--- a/internal/email/email_test.go
+++ b/internal/email/email_test.go
@@ -49,9 +49,8 @@ func TestRenderPasswordReset_ContainsBrandAndStructure(t *testing.T) {
"Sent by Jerry Koko from Koder",
"© ",
"Koder",
- "border-left:4px solid #7F56D9",
- "border-top:2px solid #7F56D9",
- "color:#53389E",
+ "border-left:4px solid #D4AF37",
+ "border-top:2px solid #D4AF37",
}
for _, want := range required {
if !strings.Contains(out, want) {
@@ -210,11 +209,9 @@ func TestRenderBestPractices_ContainsBrandAndStructure(t *testing.T) {
"mailto:support@koder.sbs",
"Koder turns every problem into an instant feedback loop.",
"© ",
- // Purple accent checks
- "border-left:4px solid #7F56D9",
- "border-top:3px solid #7F56D9",
- "border-top:2px solid #7F56D9",
- "color:#53389E",
+ // Gold accent checks
+ "border-left:4px solid #D4AF37",
+ "border-left:3px solid #D4AF37",
"box-shadow:0 4px 14px rgba(212,175,55,0.35)",
"border-bottom:2px solid #B8941F",
}
@@ -224,20 +221,10 @@ func TestRenderBestPractices_ContainsBrandAndStructure(t *testing.T) {
}
}
- // No emoji glyphs or heart entities anywhere in the output.
+ // No emoji glyphs in the output.
if strings.ContainsAny(out, "😀🔒🤖🚀✨🔥") {
t.Errorf("rendered email contains emoji characters")
}
- if strings.Contains(out, "♥") {
- t.Errorf("rendered email contains raw ♥ entity (should use SVG img)")
- }
-
- // Inline SVG icons must be present.
- for _, icon := range []string{SmallHeartIconDataURI, TrophyIconDataURI, HeartIconDataURI, SparklesIconDataURI, StarIconDataURI} {
- if !strings.Contains(out, icon) {
- t.Errorf("rendered email missing inline SVG icon data URI (truncated: %s...)", icon[:60])
- }
- }
// Stats bar should have separate Go and Python cells, not "Go / Python".
if strings.Contains(out, "Go / Python") {
@@ -359,8 +346,7 @@ func TestRenderProblemReminderString_ContainsLightThemeStyling(t *testing.T) {
"support@koder.sbs",
"mailto:support@koder.sbs",
"Koder turns every problem into an instant feedback loop.",
- "border-left:4px solid #7F56D9",
- "color:#53389E",
+ "border-left:4px solid #D4AF37",
} {
if !strings.Contains(out, want) {
t.Errorf("rendered reminder missing %q", want)
diff --git a/internal/email/problem_reminder_test.go b/internal/email/problem_reminder_test.go
index ff25554..f0dcc69 100644
--- a/internal/email/problem_reminder_test.go
+++ b/internal/email/problem_reminder_test.go
@@ -37,9 +37,7 @@ func TestRenderProblemReminder_ContainsExpectedFields(t *testing.T) {
"Open Problem",
"https://koder.sbs/logo.png",
"support@koder.sbs",
- "border-left:4px solid #7F56D9",
- "border-top:2px solid #7F56D9",
- "color:#53389E",
+ "border-left:4px solid #D4AF37",
"border-bottom:2px solid #B8941F",
"box-shadow:0 4px 14px rgba(212,175,55,0.35)",
}