Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions frontend/actions/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use server';

import { desc, eq, and } from 'drizzle-orm';
import { and,desc, eq } from 'drizzle-orm';
import { revalidatePath } from 'next/cache';

import { db } from '@/db';
import { notifications } from '@/db/schema/notifications';

import { getCurrentUser } from '@/lib/auth';

export async function getNotifications() {
Expand Down
7 changes: 4 additions & 3 deletions frontend/actions/quiz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,14 +231,15 @@ export async function submitQuizAttempt(
const earnedAfter = computeAchievements(statsAfter).filter(a => a.earned);
const newlyEarned = earnedAfter.filter(a => !earnedBefore.has(a.id));

// Trigger notifications for any newly earned achievements
// Trigger notifications for any newly earned achievements.
// title/message are stable English fallbacks; NotificationBell renders
// them dynamically in the viewer's locale using metadata.badgeId.
for (const achievement of newlyEarned) {
// Find full object to get the fancy translated string (if needed) or just generic name
await createNotification({
userId: session.id,
type: 'ACHIEVEMENT',
title: 'Achievement Unlocked!',
message: `You just earned the ${achievement.id} badge!`,
message: achievement.id,
metadata: { badgeId: achievement.id, icon: achievement.icon },
});
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/[locale]/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
} from '@/db/queries/quizzes/quiz';
import { getUserGlobalRank, getUserProfile } from '@/db/queries/users';
import { redirect } from '@/i18n/routing';
import { getCurrentUser } from '@/lib/auth';
import { computeAchievements } from '@/lib/achievements';
import { getCurrentUser } from '@/lib/auth';
import { getUserStatsForAchievements } from '@/lib/user-stats';

export async function generateMetadata({
Expand Down Expand Up @@ -216,7 +216,7 @@ export default async function DashboardPage({
totalAttempts={totalAttempts}
globalRank={globalRank}
/>
<div className="grid gap-8 lg:grid-cols-2">
<div id="stats" className="grid gap-8 scroll-mt-8 lg:grid-cols-2">
<StatsCard stats={stats} attempts={lastAttempts} />
<ActivityHeatmapCard attempts={attempts} locale={locale} currentStreak={currentStreak} />
</div>
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AppChrome } from '@/components/header/AppChrome';
import { MainSwitcher } from '@/components/header/MainSwitcher';
import { CookieBanner } from '@/components/shared/CookieBanner';
import Footer from '@/components/shared/Footer';
import { ScrollWatcher } from '@/components/shared/ScrollWatcher';
import { ThemeProvider } from '@/components/theme/ThemeProvider';
import { locales } from '@/i18n/config';
import { getCurrentUser } from '@/lib/auth';
Expand Down Expand Up @@ -78,6 +79,7 @@ export default async function LocaleLayout({
<Footer />
<Toaster position="top-right" richColors expand />
<CookieBanner />
<ScrollWatcher />
</ThemeProvider>
</NextIntlClientProvider>
);
Expand Down
10 changes: 8 additions & 2 deletions frontend/app/[locale]/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,16 @@ export default async function LeaderboardPage() {
// ── Inject star_gazer if user has starred the repo ─────────────────
// Match by GitHub login (username) or by avatar URL base
const avatarBase = user.avatar?.split('?')[0] ?? '';
const isGitHubAvatar = (() => {
try {
return new URL(avatarBase).hostname === 'avatars.githubusercontent.com';
} catch {
return false;
}
})();
const hasStarred =
stargazerLogins.has(nameLower) ||
(avatarBase.includes('avatars.githubusercontent.com') &&
stargazerAvatars.has(avatarBase));
(isGitHubAvatar && stargazerAvatars.has(avatarBase));

if (hasStarred && !achievements.some(a => a.id === 'star_gazer')) {
const def = ACHIEVEMENTS.find(a => a.id === 'star_gazer');
Expand Down
126 changes: 70 additions & 56 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,16 @@
--sponsor-hover: #bf3989;
}

@property --scroll-thumb-alpha {
syntax: '<number>';
inherits: true;
initial-value: 0;
}

html {
overflow-x: hidden;
--scroll-thumb-alpha: 0;
transition: --scroll-thumb-alpha 0.3s ease;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

*::-webkit-scrollbar {
Expand All @@ -130,22 +138,36 @@ html {
}

*::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.25);
background: rgba(0, 0, 0, var(--scroll-thumb-alpha));
border-radius: 3px;
}

:is(.dark) *::-webkit-scrollbar-thumb,
.dark::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
html:is(.dark) *::-webkit-scrollbar-thumb,
html .dark *::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, var(--scroll-thumb-alpha));
}

html.is-scrolling {
--scroll-thumb-alpha: 0.25;
}

html.is-scrolling:is(.dark),
html.is-scrolling .dark {
--scroll-thumb-alpha: 0.2;
}
Comment on lines 141 to 158
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Scrollbar thumb transition won't animate in WebKit/Blink browsers.

The transition: background 0.3s ease on *::-webkit-scrollbar-thumb (line 135) is declared but WebKit/Blink browsers do not support CSS transitions on scrollbar pseudo-elements. The scrollbar will appear/disappear instantly rather than fading. This is purely cosmetic — the functionality is correct — but the transition declaration is effectively dead code in those browsers.

The Firefox scrollbar-color transition (line 151) has better support, so the fade effect may work there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/globals.css` around lines 132 - 145, The WebKit/Blink scrollbar
pseudo-element transition is ineffective; remove the dead transition from
*::-webkit-scrollbar-thumb and implement the fade via a CSS custom property on
html (e.g., --scroll-thumb-alpha) that the thumb uses like background:
rgba(0,0,0,var(--scroll-thumb-alpha)); add a transition on the html selector (or
html.is-scrolling) to animate --scroll-thumb-alpha between 0 and the target
(0.25 / 0.2 for dark) when toggling html.is-scrolling, and update the existing
html.is-scrolling and html.is-scrolling:is(.dark) / html.is-scrolling .dark
rules to set the appropriate --scroll-thumb-alpha values so the fade works
cross-browser without relying on pseudo-element transitions.


@supports (-moz-appearance: none) {
* {
scrollbar-width: thin;
scrollbar-color: transparent transparent;
transition: scrollbar-color 0.3s ease;
}

html.is-scrolling * {
scrollbar-color: rgba(0, 0, 0, 0.25) transparent;
}

:is(.dark) * {
html.is-scrolling:is(.dark) * {
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -207,12 +229,10 @@ html {
.qa-accordion-item {
position: relative;
overflow: hidden;
background-image: linear-gradient(
90deg,
transparent 0%,
transparent 54%,
var(--qa-accent-soft, rgba(161, 161, 170, 0.22)) 100%
);
background-image: linear-gradient(90deg,
transparent 0%,
transparent 54%,
var(--qa-accent-soft, rgba(161, 161, 170, 0.22)) 100%);
}

.qa-accordion-item:hover,
Expand Down Expand Up @@ -276,33 +296,30 @@ html {
}

@keyframes wave-clip {

0%,
100% {
clip-path: polygon(
0% 50%,
15% 48%,
32% 52%,
54% 60%,
70% 62%,
84% 60%,
100% 55%,
100% 100%,
0% 100%
);
clip-path: polygon(0% 50%,
15% 48%,
32% 52%,
54% 60%,
70% 62%,
84% 60%,
100% 55%,
100% 100%,
0% 100%);
}

50% {
clip-path: polygon(
0% 65%,
16% 70%,
34% 72%,
51% 68%,
67% 58%,
84% 52%,
100% 48%,
100% 100%,
0% 100%
);
clip-path: polygon(0% 65%,
16% 70%,
34% 72%,
51% 68%,
67% 58%,
84% 52%,
100% 48%,
100% 100%,
0% 100%);
}
}

Expand All @@ -322,8 +339,7 @@ html {
}

50% {
transform: translate(var(--card-x, 0), var(--card-y, 0)) scale(1.05)
rotate(calc(var(--card-rotate, 0deg) + var(--card-rotate-offset, 0deg)));
transform: translate(var(--card-x, 0), var(--card-y, 0)) scale(1.05) rotate(calc(var(--card-rotate, 0deg) + var(--card-rotate-offset, 0deg)));
}

100% {
Expand Down Expand Up @@ -390,6 +406,10 @@ html {
perspective: 1000px;
}

.perspective-midrange {
perspective: 800px;
}

.preserve-3d {
transform-style: preserve-3d;
}
Expand All @@ -400,6 +420,7 @@ html {
}

@keyframes float {

0%,
100% {
transform: translateY(0);
Expand Down Expand Up @@ -468,16 +489,12 @@ html {
0 0 0 2px rgba(0, 0, 0, 0.4), 0 0 0 7px rgba(0, 0, 0, 0.1),
0 22px 60px rgba(0, 0, 0, 0.28);

--shop-hero-btn-success-bg: color-mix(
in oklab,
var(--shop-hero-btn-bg) 88%,
white
);
--shop-hero-btn-success-bg-hover: color-mix(
in oklab,
var(--shop-hero-btn-bg) 80%,
white
);
--shop-hero-btn-success-bg: color-mix(in oklab,
var(--shop-hero-btn-bg) 88%,
white);
--shop-hero-btn-success-bg-hover: color-mix(in oklab,
var(--shop-hero-btn-bg) 80%,
white);
--shop-hero-btn-success-shadow: 0 22px 60px rgba(0, 0, 0, 0.25);
--shop-hero-btn-success-shadow-hover: 0 28px 80px rgba(0, 0, 0, 0.32);
}
Expand Down Expand Up @@ -530,16 +547,12 @@ html {
0 0 0 2px rgba(255, 45, 85, 0.7), 0 0 0 7px rgba(255, 45, 85, 0.22),
0 22px 70px rgba(255, 45, 85, 0.38);

--shop-hero-btn-success-bg: color-mix(
in oklab,
var(--accent-primary) 82%,
black
);
--shop-hero-btn-success-bg-hover: color-mix(
in oklab,
var(--accent-primary) 72%,
black
);
--shop-hero-btn-success-bg: color-mix(in oklab,
var(--accent-primary) 82%,
black);
--shop-hero-btn-success-bg-hover: color-mix(in oklab,
var(--accent-primary) 72%,
black);
--shop-hero-btn-success-shadow: 0 22px 60px rgba(255, 45, 85, 0.45);
--shop-hero-btn-success-shadow-hover: 0 28px 80px rgba(255, 45, 85, 0.6);
}
Expand Down Expand Up @@ -599,10 +612,11 @@ html {
}

@media (prefers-reduced-motion: reduce) {

.animate-float,
.animate-spin-slow,
.animate-spin-slower,
.animate-dash-flow {
animation: none !important;
}
}
}
Loading