Remove the light mode.

This commit is contained in:
2025-12-12 23:07:29 +00:00
parent a5d69ccb86
commit 4aab8af16f
33 changed files with 501 additions and 548 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -8,11 +8,11 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<script type="module" crossorigin src="./assets/index-D4kfR6G2.js"></script>
<script type="module" crossorigin src="./assets/index-gBkQ2-yN.js"></script>
<link rel="modulepreload" crossorigin href="./assets/chart-Bcjh2pZL.js">
<link rel="stylesheet" crossorigin href="./assets/index-DfSB-nNa.css">
<link rel="stylesheet" crossorigin href="./assets/index-CUXA-hqC.css">
</head>
<body class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 font-sans transition-colors duration-300">
<body class="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 font-sans">
<div id="app"></div>
</body>
</html>
+1 -1
View File
@@ -9,7 +9,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 font-sans transition-colors duration-300">
<body class="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 font-sans">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
+2 -2
View File
@@ -30,14 +30,14 @@ onMounted(async () => {
<div v-if="loading" class="flex items-center justify-center min-h-[60vh]">
<div class="text-center">
<i class="fas fa-spinner fa-spin text-4xl text-primary-500 mb-4"></i>
<p class="text-gray-600 dark:text-gray-400">Loading dashboard...</p>
<p class="text-gray-400">Loading dashboard...</p>
</div>
</div>
<div v-else-if="error" class="flex items-center justify-center min-h-[60vh]">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-4xl text-red-500 mb-4"></i>
<p class="text-gray-600 dark:text-gray-400">{{ error }}</p>
<p class="text-gray-400">{{ error }}</p>
</div>
</div>
+3 -3
View File
@@ -244,17 +244,17 @@ const sizeClasses = {
</div>
<!-- Tooltip -->
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 px-3 py-2 bg-gray-900 dark:bg-gray-800 text-white text-xs rounded-xl opacity-0 group-hover/badge:opacity-100 transition-all duration-200 pointer-events-none whitespace-nowrap z-50 shadow-xl border border-white/10">
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 px-3 py-2 bg-gray-800 text-white text-xs rounded-xl opacity-0 group-hover/badge:opacity-100 transition-all duration-200 pointer-events-none whitespace-nowrap z-50 shadow-xl border border-white/10">
<div class="font-bold text-sm">{{ getAchievement(achievementId).name }}</div>
<div class="text-gray-300 text-[11px] mt-0.5">{{ getAchievement(achievementId).description }}</div>
<div class="absolute top-full left-1/2 -translate-x-1/2 border-[6px] border-transparent border-t-gray-900 dark:border-t-gray-800"></div>
<div class="absolute top-full left-1/2 -translate-x-1/2 border-[6px] border-transparent border-t-gray-800"></div>
</div>
</div>
<!-- Label (optional) - no truncation -->
<span
v-if="showLabel"
class="text-[11px] font-medium text-gray-600 dark:text-gray-400 text-center leading-tight"
class="text-[11px] font-medium text-gray-400 text-center leading-tight"
style="max-width: 72px; word-wrap: break-word;"
>
{{ getAchievement(achievementId).name }}
+15 -15
View File
@@ -255,7 +255,7 @@ const remainingCount = computed(() => {
<div
v-for="item in progressItems"
:key="item.id"
class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
class="bg-gray-800/50 rounded-xl p-4 hover:bg-gray-800 transition-colors"
>
<div class="flex items-start justify-between mb-3">
<div class="flex items-center space-x-3">
@@ -266,30 +266,30 @@ const remainingCount = computed(() => {
<i class="fas text-white text-sm" :class="item.icon"></i>
</div>
<div>
<div class="text-sm font-semibold text-gray-900 dark:text-white">
<div class="text-sm font-semibold text-white">
{{ item.name }}
</div>
<div class="flex items-center space-x-2 text-xs text-gray-600 dark:text-gray-400">
<div class="flex items-center space-x-2 text-xs text-gray-400">
<span>{{ item.category }}</span>
<span class="text-gray-600 dark:text-gray-400"></span>
<span class="text-gray-400"></span>
<span class="font-medium">Tier {{ item.tierIndex }}/{{ item.totalTiers }}</span>
</div>
</div>
</div>
<div class="text-right">
<div class="text-sm font-bold" :class="item.isClose ? 'text-green-500' : 'text-gray-700 dark:text-gray-200'">
<div class="text-sm font-bold" :class="item.isClose ? 'text-green-500' : 'text-gray-200'">
{{ formatNumber(item.currentValue) }}
<span class="text-gray-600 dark:text-gray-400 font-normal">/</span>
<span class="text-gray-600 dark:text-gray-400 font-medium">{{ formatNumber(item.target) }}</span>
<span class="text-gray-400 font-normal">/</span>
<span class="text-gray-400 font-medium">{{ formatNumber(item.target) }}</span>
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-0.5">
<div class="text-xs text-gray-400 mt-0.5">
{{ item.remaining > 0 ? `${formatNumber(item.remaining)} to go` : 'Ready to claim!' }}
</div>
</div>
</div>
<!-- Progress Bar -->
<div class="h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div class="h-2.5 bg-gray-700 rounded-full overflow-hidden">
<div
class="h-full rounded-full transition-all duration-500 ease-out"
:class="item.progressColor"
@@ -304,14 +304,14 @@ const remainingCount = computed(() => {
v-for="(t, idx) in item.allTiers.slice(0, 5)"
:key="t.threshold"
class="w-1.5 h-1.5 rounded-full"
:class="idx < item.tierIndex ? 'bg-green-500' : 'bg-gray-300 dark:bg-gray-600'"
:class="idx < item.tierIndex ? 'bg-green-500' : 'bg-gray-600'"
:title="`Tier ${idx + 1}: ${t.name} (${formatNumber(t.threshold)})`"
></span>
<span v-if="item.totalTiers > 5" class="text-[10px] text-gray-600 dark:text-gray-400">+{{ item.totalTiers - 5 }}</span>
<span v-if="item.totalTiers > 5" class="text-[10px] text-gray-400">+{{ item.totalTiers - 5 }}</span>
</div>
<span
class="text-xs font-semibold"
:class="item.isClose ? 'text-green-500' : 'text-gray-600 dark:text-gray-400'"
:class="item.isClose ? 'text-green-500' : 'text-gray-400'"
>
{{ item.progress }}%
</span>
@@ -319,16 +319,16 @@ const remainingCount = computed(() => {
</div>
<!-- Show more indicator -->
<div v-if="remainingCount > 0" class="text-center text-xs text-gray-600 dark:text-gray-400 pt-2">
<div v-if="remainingCount > 0" class="text-center text-xs text-gray-400 pt-2">
+{{ remainingCount }} more achievements to unlock
</div>
<!-- Empty state -->
<div v-if="!progressItems.length" class="text-center py-8 text-gray-600 dark:text-gray-400">
<div v-if="!progressItems.length" class="text-center py-8 text-gray-400">
<div class="w-16 h-16 mx-auto mb-3 rounded-2xl bg-gradient-to-br from-yellow-400 to-amber-500 flex items-center justify-center shadow-lg">
<i class="fas fa-trophy text-2xl text-white"></i>
</div>
<p class="font-medium text-gray-700 dark:text-gray-300">All achievements unlocked!</p>
<p class="font-medium text-gray-300">All achievements unlocked!</p>
<p class="text-sm mt-1">You're a legend!</p>
</div>
</div>
+2 -2
View File
@@ -11,7 +11,7 @@ defineProps({
</script>
<template>
<div class="flex items-center space-x-2 text-sm text-gray-600 dark:text-gray-400 mb-6">
<div class="flex items-center space-x-2 text-sm text-gray-400 mb-6">
<template v-for="(item, index) in items" :key="index">
<RouterLink
v-if="item.to"
@@ -22,7 +22,7 @@ defineProps({
</RouterLink>
<span
v-else
:class="index === items.length - 1 ? 'text-gray-900 dark:text-white' : ''"
:class="index === items.length - 1 ? 'text-white' : ''"
>
{{ item.label }}
</span>
+1 -1
View File
@@ -8,7 +8,7 @@ defineProps({
<template>
<div
:class="[
'rounded-xl bg-white dark:bg-gray-800 text-gray-900 dark:text-white border border-gray-200 dark:border-gray-700 shadow',
'rounded-xl bg-gray-800 text-white border border-gray-700 shadow',
padding ? 'p-6' : '',
hover ? 'hover:shadow-lg transition-shadow' : ''
]"
+8 -8
View File
@@ -37,7 +37,7 @@ defineProps({
:src="contributor.avatar_url"
:name="contributor.login"
:size="featured ? 'xl' : 'lg'"
class="ring-2 ring-gray-100 dark:ring-gray-700"
class="ring-2 ring-gray-700"
/>
<RankBadge
v-if="showRank && rank > 0"
@@ -47,10 +47,10 @@ defineProps({
/>
</div>
<div>
<h3 class="font-bold text-lg text-gray-900 dark:text-white group-hover:text-primary-500 transition-colors">
<h3 class="font-bold text-lg text-white group-hover:text-primary-500 transition-colors">
{{ contributor.name || contributor.login }}
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">
<p class="text-sm text-gray-400">
@{{ contributor.login }}
</p>
<p v-if="contributor.team" class="text-xs text-accent-500 mt-0.5">{{ contributor.team }}</p>
@@ -59,16 +59,16 @@ defineProps({
</div>
<!-- Score display -->
<div class="flex items-center justify-between py-3 px-4 -mx-2 rounded-lg bg-gradient-to-r from-primary-50 to-accent-50 dark:from-primary-900/20 dark:to-accent-900/20 mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Score</span>
<span class="text-2xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
<div class="flex items-center justify-between py-3 px-4 -mx-2 rounded-lg bg-gradient-to-r from-primary-900/20 to-accent-900/20 mb-4">
<span class="text-sm font-medium text-gray-300">Score</span>
<span class="text-2xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
{{ formatNumber(contributor.score?.total || contributor.score || 0) }}
</span>
</div>
<!-- Achievements -->
<div v-if="contributor.achievements?.length" class="mt-auto">
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">Achievements</div>
<div class="text-xs font-medium text-gray-400 mb-2">Achievements</div>
<div class="flex flex-wrap gap-1.5">
<AchievementBadge
v-for="achievement in contributor.achievements.slice(0, 8)"
@@ -78,7 +78,7 @@ defineProps({
/>
<span
v-if="contributor.achievements.length > 8"
class="inline-flex items-center justify-center px-2 h-7 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-bold"
class="inline-flex items-center justify-center px-2 h-7 rounded-lg bg-gray-700 text-gray-300 text-xs font-bold"
>
+{{ contributor.achievements.length - 8 }}
</span>
+3 -3
View File
@@ -30,7 +30,7 @@ defineProps({
class="ring-2 ring-transparent group-hover:ring-primary-500 transition-all"
/>
<div>
<div class="font-medium text-gray-900 dark:text-white group-hover:text-primary-500 transition-colors">
<div class="font-medium text-white group-hover:text-primary-500 transition-colors">
{{ contributor.name || contributor.login }}
</div>
<div class="text-sm">
@@ -39,13 +39,13 @@ defineProps({
:href="`https://github.com/${contributor.login}`"
target="_blank"
rel="noopener noreferrer"
class="font-medium text-gray-800 dark:text-gray-400 hover:text-primary-500 transition-colors"
class="font-medium text-gray-400 hover:text-primary-500 transition-colors"
@click.stop
>
@{{ contributor.login }}
<i class="fas fa-external-link-alt text-xs ml-1 opacity-50"></i>
</a>
<span v-else class="font-medium text-gray-800 dark:text-gray-400">
<span v-else class="font-medium text-gray-400">
@{{ contributor.login }}
</span>
</div>
+6 -6
View File
@@ -21,7 +21,7 @@ defineProps({
},
rowClass: {
type: String,
default: 'hover:bg-gray-50 dark:hover:bg-gray-800/30 transition'
default: 'hover:bg-gray-800/30 transition'
},
clickableRows: {
type: Boolean,
@@ -43,13 +43,13 @@ const getAlignClass = (align) => {
<template>
<Card :padding="false" class="overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 dark:bg-gray-800/50">
<thead class="bg-gray-800/50">
<tr>
<th
v-for="col in columns"
:key="col.key"
:class="[
'px-3 sm:px-6 py-3 sm:py-4 text-xs font-semibold text-gray-700 dark:text-gray-400 uppercase tracking-wider',
'px-3 sm:px-6 py-3 sm:py-4 text-xs font-semibold text-gray-400 uppercase tracking-wider',
getAlignClass(col.align),
col.headerClass
]"
@@ -58,7 +58,7 @@ const getAlignClass = (align) => {
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<tbody class="divide-y divide-gray-700">
<tr
v-for="(item, index) in items"
:key="item.id || item.login || index"
@@ -80,8 +80,8 @@ const getAlignClass = (align) => {
<!-- Empty State -->
<div v-if="!items.length" class="text-center py-12">
<i :class="emptyIcon" class="text-4xl text-gray-400 dark:text-gray-600 mb-4"></i>
<p class="text-gray-600 dark:text-gray-400">{{ emptyMessage }}</p>
<i :class="emptyIcon" class="text-4xl text-gray-600 mb-4"></i>
<p class="text-gray-400">{{ emptyMessage }}</p>
</div>
</Card>
</template>
+1 -1
View File
@@ -15,7 +15,7 @@ defineProps({
<div class="flex items-center justify-center min-h-[60vh]">
<div class="text-center">
<i :class="icon" class="text-4xl text-red-500 mb-4"></i>
<p class="text-gray-600 dark:text-gray-400">{{ message }}</p>
<p class="text-gray-400">{{ message }}</p>
<slot name="actions"></slot>
</div>
</div>
+4 -4
View File
@@ -14,20 +14,20 @@ const generatedAt = computed(() => {
</script>
<template>
<footer class="py-8 px-4 mt-16 border-t border-gray-200 dark:border-gray-700">
<footer class="py-8 px-4 mt-16 border-t border-gray-700">
<div class="container mx-auto text-center">
<p class="text-gray-700 dark:text-gray-400">
<p class="text-gray-400">
Generated by
<a
href="https://github.com/lukaszraczylo/git-velocity"
class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-medium"
class="text-primary-400 hover:text-primary-300 font-medium"
target="_blank"
rel="noopener noreferrer"
>
Git Velocity
</a>
</p>
<p v-if="generatedAt" class="text-sm text-gray-600 dark:text-gray-500 mt-2">
<p v-if="generatedAt" class="text-sm text-gray-500 mt-2">
{{ generatedAt }}
</p>
</div>
+1 -1
View File
@@ -11,7 +11,7 @@ defineProps({
<div class="flex items-center justify-center min-h-[60vh]">
<div class="text-center">
<i class="fas fa-spinner fa-spin text-4xl text-primary-500 mb-4"></i>
<p class="text-gray-600 dark:text-gray-400">{{ message }}</p>
<p class="text-gray-400">{{ message }}</p>
</div>
</div>
</template>
+12 -12
View File
@@ -28,37 +28,37 @@ defineProps({
<div class="flex items-center space-x-4 mb-4">
<Avatar :src="member.avatar_url" :name="member.login" size="lg" />
<div>
<h3 class="font-semibold text-gray-900 dark:text-white">
<h3 class="font-semibold text-white">
{{ member.name || member.login }}
</h3>
<p class="text-sm text-gray-800 dark:text-gray-400">@{{ member.login }}</p>
<p class="text-sm text-gray-400">@{{ member.login }}</p>
</div>
</div>
<div class="grid grid-cols-3 gap-4 text-center mb-4">
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
<div class="text-lg font-semibold text-white">
{{ formatNumber(member.commit_count) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Commits</div>
<div class="text-xs text-gray-400">Commits</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
<div class="text-lg font-semibold text-white">
{{ formatNumber(member.prs_opened) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">PRs</div>
<div class="text-xs text-gray-400">PRs</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
<div class="text-lg font-semibold text-white">
{{ formatNumber(member.reviews_given) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Reviews</div>
<div class="text-xs text-gray-400">Reviews</div>
</div>
</div>
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700">
<span class="text-sm text-gray-600 dark:text-gray-400">Score</span>
<span class="text-xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
<div class="flex items-center justify-between pt-4 border-t border-gray-700">
<span class="text-sm text-gray-400">Score</span>
<span class="text-xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
{{ formatNumber(member.score?.total || 0) }}
</span>
</div>
@@ -72,7 +72,7 @@ defineProps({
/>
<span
v-if="member.achievements.length > 4"
class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-bold"
class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gray-700 text-gray-300 text-xs font-bold"
>
+{{ member.achievements.length - 4 }}
</span>
+19 -24
View File
@@ -1,7 +1,6 @@
<script setup>
import { ref, inject, computed } from 'vue'
import { RouterLink, useRoute } from 'vue-router'
import ThemeToggle from './ThemeToggle.vue'
const route = useRoute()
const globalData = inject('globalData')
@@ -11,32 +10,32 @@ const repositories = computed(() => globalData.value?.Repositories || [])
</script>
<template>
<nav class="sticky top-0 z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-700 shadow-lg">
<nav class="sticky top-0 z-50 bg-gray-900/80 backdrop-blur-md border-b border-gray-700 shadow-lg">
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<!-- Logo -->
<RouterLink to="/" class="flex items-center space-x-2">
<i class="fas fa-rocket text-2xl bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent"></i>
<span class="text-xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">Git Velocity</span>
<i class="fas fa-rocket text-2xl bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent"></i>
<span class="text-xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">Git Velocity</span>
</RouterLink>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-6">
<RouterLink
to="/"
:class="route.path === '/' ? 'text-primary-500 font-medium' : 'text-gray-800 dark:text-gray-200 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors'"
:class="route.path === '/' ? 'text-primary-500 font-medium' : 'text-gray-200 font-medium hover:text-primary-400 transition-colors'"
>
Dashboard
</RouterLink>
<RouterLink
to="/leaderboard"
:class="route.path === '/leaderboard' ? 'text-primary-500 font-medium' : 'text-gray-800 dark:text-gray-200 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors'"
:class="route.path === '/leaderboard' ? 'text-primary-500 font-medium' : 'text-gray-200 font-medium hover:text-primary-400 transition-colors'"
>
Leaderboard
</RouterLink>
<RouterLink
to="/how-scoring-works"
:class="route.path === '/how-scoring-works' ? 'text-primary-500 font-medium' : 'text-gray-800 dark:text-gray-200 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors'"
:class="route.path === '/how-scoring-works' ? 'text-primary-500 font-medium' : 'text-gray-200 font-medium hover:text-primary-400 transition-colors'"
>
How Scoring Works
</RouterLink>
@@ -44,27 +43,23 @@ const repositories = computed(() => globalData.value?.Repositories || [])
v-for="repo in repositories"
:key="`${repo.Owner}/${repo.Name}`"
:to="`/repos/${repo.Owner}/${repo.Name}`"
:class="route.path.includes(`/repos/${repo.Owner}/${repo.Name}`) ? 'text-primary-500 font-medium' : 'text-gray-800 dark:text-gray-200 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors'"
:class="route.path.includes(`/repos/${repo.Owner}/${repo.Name}`) ? 'text-primary-500 font-medium' : 'text-gray-200 font-medium hover:text-primary-400 transition-colors'"
>
{{ repo.Name }}
</RouterLink>
</div>
<!-- Actions -->
<div class="flex items-center space-x-4">
<ThemeToggle />
<!-- Mobile Menu Button -->
<button
@click="mobileMenuOpen = !mobileMenuOpen"
class="md:hidden p-2 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition"
class="md:hidden p-2 rounded-lg hover:bg-gray-700 transition"
>
<i class="fas fa-bars text-gray-700 dark:text-gray-200"></i>
<i class="fas fa-bars text-gray-200"></i>
</button>
</div>
</div>
<!-- Mobile Menu -->
<div v-if="mobileMenuOpen" class="md:hidden py-2 border-t border-gray-200 dark:border-gray-700">
<div v-if="mobileMenuOpen" class="md:hidden py-2 border-t border-gray-700">
<div class="flex flex-col space-y-1">
<RouterLink
to="/"
@@ -72,8 +67,8 @@ const repositories = computed(() => globalData.value?.Repositories || [])
:class="[
'block px-4 py-3 rounded-lg text-base font-medium transition-colors',
route.path === '/'
? 'bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'
? 'bg-primary-900/20 text-primary-400'
: 'text-gray-200 hover:bg-gray-800'
]"
>
<i class="fas fa-home mr-3 w-5 text-center"></i>Dashboard
@@ -84,8 +79,8 @@ const repositories = computed(() => globalData.value?.Repositories || [])
:class="[
'block px-4 py-3 rounded-lg text-base font-medium transition-colors',
route.path === '/leaderboard'
? 'bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'
? 'bg-primary-900/20 text-primary-400'
: 'text-gray-200 hover:bg-gray-800'
]"
>
<i class="fas fa-trophy mr-3 w-5 text-center"></i>Leaderboard
@@ -96,8 +91,8 @@ const repositories = computed(() => globalData.value?.Repositories || [])
:class="[
'block px-4 py-3 rounded-lg text-base font-medium transition-colors',
route.path === '/how-scoring-works'
? 'bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'
? 'bg-primary-900/20 text-primary-400'
: 'text-gray-200 hover:bg-gray-800'
]"
>
<i class="fas fa-calculator mr-3 w-5 text-center"></i>How Scoring Works
@@ -110,8 +105,8 @@ const repositories = computed(() => globalData.value?.Repositories || [])
:class="[
'block px-4 py-3 rounded-lg text-base font-medium transition-colors',
route.path.includes(`/repos/${repo.Owner}/${repo.Name}`)
? 'bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'
? 'bg-primary-900/20 text-primary-400'
: 'text-gray-200 hover:bg-gray-800'
]"
>
<i class="fas fa-code-branch mr-3 w-5 text-center"></i>{{ repo.Name }}
+2 -2
View File
@@ -38,11 +38,11 @@ defineProps({
<slot name="prefix"></slot>
<h1 class="text-4xl font-bold mb-4">
<i v-if="icon" :class="[icon, iconColor]" class="mr-3"></i>
<span class="bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">{{ title }}</span>
<span class="bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">{{ title }}</span>
</h1>
</div>
<p v-if="subtitle || $slots.subtitle" class="text-gray-600 dark:text-gray-300">
<p v-if="subtitle || $slots.subtitle" class="text-gray-300">
<slot name="subtitle">{{ subtitle }}</slot>
</p>
+1 -1
View File
@@ -16,7 +16,7 @@ const rankClass = computed(() => {
if (props.rank === 1) return 'bg-gradient-to-r from-yellow-400 to-amber-500'
if (props.rank === 2) return 'bg-gradient-to-r from-slate-400 to-slate-500'
if (props.rank === 3) return 'bg-gradient-to-r from-amber-600 to-amber-700'
return 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300'
return 'bg-gray-700 text-gray-300'
})
const classes = computed(() => sizeClasses[props.size] || sizeClasses.md)
+9 -9
View File
@@ -18,31 +18,31 @@ defineProps({
>
<Card hover>
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-gray-900 dark:text-white group-hover:text-primary-500 transition">
<h3 class="font-semibold text-white group-hover:text-primary-500 transition">
{{ repo.name }}
</h3>
<i class="fas fa-arrow-right text-gray-600 dark:text-gray-400 group-hover:text-primary-500 transition"></i>
<i class="fas fa-arrow-right text-gray-400 group-hover:text-primary-500 transition"></i>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">{{ repo.owner }}/{{ repo.name }}</p>
<p class="text-sm text-gray-400 mb-4">{{ repo.owner }}/{{ repo.name }}</p>
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
<div class="text-lg font-semibold text-white">
{{ formatNumber(repo.total_commits) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Commits</div>
<div class="text-xs text-gray-400">Commits</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
<div class="text-lg font-semibold text-white">
{{ formatNumber(repo.total_prs) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">PRs</div>
<div class="text-xs text-gray-400">PRs</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
<div class="text-lg font-semibold text-white">
{{ repo.active_contributors }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Contributors</div>
<div class="text-xs text-gray-400">Contributors</div>
</div>
</div>
</Card>
+1 -1
View File
@@ -16,7 +16,7 @@ defineProps({
</script>
<template>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">
<h2 class="text-2xl font-bold text-white mb-6">
<i v-if="icon" :class="[icon, iconColor]" class="mr-2"></i>{{ title }}
<slot name="suffix"></slot>
</h2>
+2 -2
View File
@@ -7,7 +7,7 @@ defineProps({
label: { type: String, required: true },
icon: { type: String, default: '' },
iconColor: { type: String, default: 'text-gray-500' },
valueClass: { type: String, default: 'bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent' },
valueClass: { type: String, default: 'bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent' },
delay: { type: String, default: '0s' }
})
</script>
@@ -19,7 +19,7 @@ defineProps({
<div class="text-xl sm:text-2xl md:text-3xl font-bold truncate" :class="valueClass">
{{ typeof value === 'number' ? formatNumber(value) : value }}
</div>
<div class="text-xs sm:text-sm text-gray-700 dark:text-gray-400 mt-1 truncate">{{ label }}</div>
<div class="text-xs sm:text-sm text-gray-400 mt-1 truncate">{{ label }}</div>
</div>
<div v-if="icon" class="text-2xl sm:text-3xl opacity-50 ml-2 flex-shrink-0" :class="iconColor">
<i :class="icon"></i>
+6 -6
View File
@@ -19,7 +19,7 @@ defineProps({
>
<Card hover>
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-gray-900 dark:text-white group-hover:text-primary-500 transition">
<h3 class="font-semibold text-white group-hover:text-primary-500 transition">
{{ team.name }}
</h3>
<span
@@ -34,7 +34,7 @@ defineProps({
</template>
<span
v-if="(team.members?.length || 0) > 5"
class="w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center text-gray-600 dark:text-gray-300 text-xs font-bold"
class="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center text-gray-300 text-xs font-bold"
>
+{{ team.members.length - 5 }}
</span>
@@ -42,16 +42,16 @@ defineProps({
<div class="grid grid-cols-2 gap-4 text-center">
<div>
<div class="text-lg font-semibold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
<div class="text-lg font-semibold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
{{ formatNumber(team.total_score) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Total Score</div>
<div class="text-xs text-gray-400">Total Score</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
<div class="text-lg font-semibold text-white">
{{ team.members?.length || 0 }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Members</div>
<div class="text-xs text-gray-400">Members</div>
</div>
</div>
</Card>
-48
View File
@@ -1,48 +0,0 @@
<script setup>
import { ref, onMounted, watch } from 'vue'
const isDark = ref(false)
onMounted(() => {
const savedTheme = localStorage.getItem('theme')
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
isDark.value = savedTheme === 'dark' || (!savedTheme && prefersDark)
updateTheme()
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
isDark.value = e.matches
updateTheme()
}
})
})
watch(isDark, () => {
updateTheme()
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
})
function updateTheme() {
if (isDark.value) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}
function toggle() {
isDark.value = !isDark.value
}
</script>
<template>
<button
@click="toggle"
class="p-2 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition"
:aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
>
<i v-if="isDark" class="fas fa-moon text-purple-400"></i>
<i v-else class="fas fa-sun text-yellow-500"></i>
</button>
</template>
+19 -3
View File
@@ -51,6 +51,15 @@ const chartData = computed(() => {
const isMobile = ref(window.innerWidth < 640)
// Dark mode colors
const themeColors = {
gridColor: 'rgba(255, 255, 255, 0.1)',
textColor: 'rgba(255, 255, 255, 0.7)',
tooltipBg: 'rgba(30, 30, 30, 0.95)',
tooltipText: '#fff',
tooltipBorder: 'rgba(255, 255, 255, 0.1)'
}
const chartOptions = computed(() => ({
responsive: true,
maintainAspectRatio: false,
@@ -65,13 +74,18 @@ const chartOptions = computed(() => ({
usePointStyle: true,
padding: isMobile.value ? 10 : 20,
boxWidth: isMobile.value ? 8 : 12,
color: themeColors.textColor,
font: {
size: isMobile.value ? 10 : 12
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
backgroundColor: themeColors.tooltipBg,
titleColor: themeColors.tooltipText,
bodyColor: themeColors.tooltipText,
borderColor: themeColors.tooltipBorder,
borderWidth: 1,
padding: isMobile.value ? 8 : 12,
titleFont: {
size: isMobile.value ? 12 : 14
@@ -92,6 +106,7 @@ const chartOptions = computed(() => ({
display: false
},
ticks: {
color: themeColors.textColor,
font: {
size: isMobile.value ? 9 : 11
},
@@ -103,9 +118,10 @@ const chartOptions = computed(() => ({
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.05)'
color: themeColors.gridColor
},
ticks: {
color: themeColors.textColor,
font: {
size: isMobile.value ? 9 : 11
},
@@ -177,7 +193,7 @@ watch(() => props.showScore, () => {
<div class="velocity-chart" :style="{ height }">
<canvas ref="chartRef"></canvas>
<div v-if="!timeline?.labels?.length" class="flex items-center justify-center h-full">
<p class="text-gray-500 dark:text-gray-400">No velocity data available</p>
<p class="text-gray-400">No velocity data available</p>
</div>
</div>
</template>
-10
View File
@@ -31,13 +31,3 @@
100% { opacity: 1; transform: translateY(0); }
}
}
/* Dark mode body background - Tailwind v4 doesn't process dark: variants on body properly */
.dark body,
body.dark {
background-image: linear-gradient(to bottom right, var(--color-gray-900), var(--color-gray-800));
}
html.dark {
background-color: var(--color-gray-900);
}
+63 -63
View File
@@ -103,23 +103,23 @@ watch(globalData, loadContributor)
/>
<div class="text-center md:text-left">
<h1 class="text-4xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
<h1 class="text-4xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
{{ contributor.name || contributor.login }}
</h1>
<p class="text-xl text-gray-600 dark:text-gray-400 mt-1">
<p class="text-xl text-gray-400 mt-1">
<GithubLink :url="`https://github.com/${contributor.login}`">
@{{ contributor.login }}
</GithubLink>
</p>
<div class="flex items-center justify-center md:justify-start space-x-4 mt-4">
<div class="bg-gradient-to-r from-pink-400/10 to-purple-400/10 dark:from-pink-400/5 dark:to-purple-400/5 border border-pink-400/20 dark:border-pink-400/10 rounded-lg px-4 py-2">
<span class="text-sm text-gray-600 dark:text-gray-400">Score:</span>
<span class="text-2xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent ml-2">
<div class="bg-gradient-to-r from-pink-400/5 to-purple-400/5 border border-pink-400/10 rounded-lg px-4 py-2">
<span class="text-sm text-gray-400">Score:</span>
<span class="text-2xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent ml-2">
{{ formatNumber(contributor.score?.total || contributor.score || 0) }}
</span>
</div>
<div v-if="contributor.score?.rank" class="text-sm text-gray-600 dark:text-gray-400">
<div v-if="contributor.score?.rank" class="text-sm text-gray-400">
Rank #{{ contributor.score.rank }}
<span v-if="contributor.score?.percentile_rank">
(Top {{ formatPercent(contributor.score.percentile_rank) }})
@@ -179,56 +179,56 @@ watch(globalData, loadContributor)
<div class="grid md:grid-cols-2 gap-6">
<!-- Code Stats -->
<Card>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
<h3 class="text-lg font-semibold text-white mb-4">
<i class="fas fa-code text-green-500 mr-2"></i>Code Contributions
</h3>
<div class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Lines Added</span>
<span class="text-gray-300">Lines Added</span>
<span class="text-green-500 font-semibold">
+{{ formatNumber(contributor.lines_added || 0) }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Lines Deleted</span>
<span class="text-gray-300">Lines Deleted</span>
<span class="text-red-500 font-semibold">
-{{ formatNumber(contributor.lines_deleted || 0) }}
</span>
</div>
<div v-if="contributor.meaningful_lines_added !== undefined" class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Meaningful Lines Added</span>
<span class="text-gray-300">Meaningful Lines Added</span>
<span class="text-emerald-500 font-semibold">
+{{ formatNumber(contributor.meaningful_lines_added || 0) }}
</span>
</div>
<div v-if="contributor.meaningful_lines_deleted !== undefined" class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Meaningful Lines Deleted</span>
<span class="text-gray-300">Meaningful Lines Deleted</span>
<span class="text-rose-500 font-semibold">
-{{ formatNumber(contributor.meaningful_lines_deleted || 0) }}
</span>
</div>
<div v-if="contributor.comment_lines_added !== undefined" class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Comment Lines Added</span>
<span class="text-gray-300">Comment Lines Added</span>
<span class="text-cyan-500 font-semibold">
+{{ formatNumber(contributor.comment_lines_added || 0) }}
</span>
</div>
<div v-if="contributor.comment_lines_deleted !== undefined" class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Comment Lines Deleted</span>
<span class="text-gray-300">Comment Lines Deleted</span>
<span class="text-amber-500 font-semibold">
-{{ formatNumber(contributor.comment_lines_deleted || 0) }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Files Changed</span>
<span class="text-gray-900 dark:text-white font-semibold">
<span class="text-gray-300">Files Changed</span>
<span class="text-white font-semibold">
{{ formatNumber(contributor.files_changed || 0) }}
</span>
</div>
<div v-if="contributor.avg_pr_size" class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Avg PR Size</span>
<span class="text-gray-900 dark:text-white font-semibold">
<span class="text-gray-300">Avg PR Size</span>
<span class="text-white font-semibold">
{{ formatNumber(Math.round(contributor.avg_pr_size)) }} lines
</span>
</div>
@@ -237,38 +237,38 @@ watch(globalData, loadContributor)
<!-- Review Stats -->
<Card>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
<h3 class="text-lg font-semibold text-white mb-4">
<i class="fas fa-comments text-purple-500 mr-2"></i>Review Activity
</h3>
<div class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Reviews Given</span>
<span class="text-gray-900 dark:text-white font-semibold">
<span class="text-gray-300">Reviews Given</span>
<span class="text-white font-semibold">
{{ formatNumber(contributor.reviews_given || 0) }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Approvals</span>
<span class="text-gray-300">Approvals</span>
<span class="text-green-500 font-semibold">
{{ formatNumber(contributor.approvals_given || 0) }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Changes Requested</span>
<span class="text-gray-300">Changes Requested</span>
<span class="text-orange-500 font-semibold">
{{ formatNumber(contributor.changes_requested || 0) }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Review Comments</span>
<span class="text-gray-900 dark:text-white font-semibold">
<span class="text-gray-300">Review Comments</span>
<span class="text-white font-semibold">
{{ formatNumber(contributor.review_comments || 0) }}
</span>
</div>
<div v-if="contributor.avg_review_time_hours" class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Avg Review Time</span>
<span class="text-gray-900 dark:text-white font-semibold">
<span class="text-gray-300">Avg Review Time</span>
<span class="text-white font-semibold">
{{ formatDuration(contributor.avg_review_time_hours) }}
</span>
</div>
@@ -277,31 +277,31 @@ watch(globalData, loadContributor)
<!-- Issue Stats -->
<Card v-if="contributor.issues_opened || contributor.issues_closed || contributor.issue_comments || contributor.issue_references_in_commits">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
<h3 class="text-lg font-semibold text-white mb-4">
<i class="fas fa-bug text-red-500 mr-2"></i>Issue Activity
</h3>
<div class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Issues Opened</span>
<span class="text-gray-300">Issues Opened</span>
<span class="text-red-500 font-semibold">
{{ formatNumber(contributor.issues_opened || 0) }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Issues Closed</span>
<span class="text-gray-300">Issues Closed</span>
<span class="text-green-500 font-semibold">
{{ formatNumber(contributor.issues_closed || 0) }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Issue Comments</span>
<span class="text-gray-300">Issue Comments</span>
<span class="text-blue-500 font-semibold">
{{ formatNumber(contributor.issue_comments || 0) }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Issue References in Commits</span>
<span class="text-gray-300">Issue References in Commits</span>
<span class="text-purple-500 font-semibold">
{{ formatNumber(contributor.issue_references_in_commits || 0) }}
</span>
@@ -316,66 +316,66 @@ watch(globalData, loadContributor)
<section v-if="contributor.score?.breakdown" class="py-8 px-4">
<div class="container mx-auto">
<Card>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
<i class="fas fa-chart-pie bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent mr-2"></i>Score Breakdown
<h3 class="text-lg font-semibold text-white mb-4">
<i class="fas fa-chart-pie bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent mr-2"></i>Score Breakdown
</h3>
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-4">
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
<div class="text-center p-4 rounded-lg bg-gray-800/50">
<div class="text-2xl font-bold text-green-500">
{{ formatNumber(contributor.score.breakdown.commits || 0) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Commits</div>
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.commit_count || 0 }} × 10 pts</div>
<div class="text-xs text-gray-400 mt-1">Commits</div>
<div class="text-xs text-gray-400">{{ contributor.commit_count || 0 }} × 10 pts</div>
</div>
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
<div class="text-center p-4 rounded-lg bg-gray-800/50">
<div class="text-2xl font-bold text-blue-500">
{{ formatNumber(contributor.score.breakdown.prs || 0) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">PRs</div>
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.prs_opened || 0 }} opened + {{ contributor.prs_merged || 0 }} merged</div>
<div class="text-xs text-gray-400 mt-1">PRs</div>
<div class="text-xs text-gray-400">{{ contributor.prs_opened || 0 }} opened + {{ contributor.prs_merged || 0 }} merged</div>
</div>
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
<div class="text-center p-4 rounded-lg bg-gray-800/50">
<div class="text-2xl font-bold text-purple-500">
{{ formatNumber(contributor.score.breakdown.reviews || 0) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Reviews</div>
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.reviews_given || 0 }} × 30 pts</div>
<div class="text-xs text-gray-400 mt-1">Reviews</div>
<div class="text-xs text-gray-400">{{ contributor.reviews_given || 0 }} × 30 pts</div>
</div>
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
<div class="text-center p-4 rounded-lg bg-gray-800/50">
<div class="text-2xl font-bold text-pink-500">
{{ formatNumber(contributor.score.breakdown.comments || 0) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Comments</div>
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.review_comments || 0 }} × 5 pts</div>
<div class="text-xs text-gray-400 mt-1">Comments</div>
<div class="text-xs text-gray-400">{{ contributor.review_comments || 0 }} × 5 pts</div>
</div>
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
<div class="text-center p-4 rounded-lg bg-gray-800/50">
<div class="text-2xl font-bold text-red-500">
{{ formatNumber(contributor.score.breakdown.issues || 0) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Issues</div>
<div class="text-xs text-gray-600 dark:text-gray-400">opened, closed, comments, refs</div>
<div class="text-xs text-gray-400 mt-1">Issues</div>
<div class="text-xs text-gray-400">opened, closed, comments, refs</div>
</div>
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
<div class="text-center p-4 rounded-lg bg-gray-800/50">
<div class="text-2xl font-bold text-orange-500">
{{ formatNumber(contributor.score.breakdown.line_changes || 0) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Line Changes</div>
<div class="text-xs text-gray-600 dark:text-gray-400">meaningful lines × 0.1 pts</div>
<div class="text-xs text-gray-400 mt-1">Line Changes</div>
<div class="text-xs text-gray-400">meaningful lines × 0.1 pts</div>
</div>
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
<div class="text-center p-4 rounded-lg bg-gray-800/50">
<div class="text-2xl font-bold text-yellow-500">
{{ formatNumber(contributor.score.breakdown.response_bonus || 0) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Response Bonus</div>
<div class="text-xs text-gray-600 dark:text-gray-400">fast review bonus</div>
<div class="text-xs text-gray-400 mt-1">Response Bonus</div>
<div class="text-xs text-gray-400">fast review bonus</div>
</div>
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
<div class="text-center p-4 rounded-lg bg-gray-800/50">
<div class="text-2xl font-bold text-indigo-500">
{{ formatNumber(contributor.score.breakdown.out_of_hours || 0) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Out of Hours</div>
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.out_of_hours_count || 0 }} × 2 pts</div>
<div class="text-xs text-gray-400 mt-1">Out of Hours</div>
<div class="text-xs text-gray-400">{{ contributor.out_of_hours_count || 0 }} × 2 pts</div>
</div>
</div>
</Card>
@@ -389,8 +389,8 @@ watch(globalData, loadContributor)
<!-- Earned Achievements -->
<Card v-if="contributor.achievements?.length">
<div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
<i class="fas fa-award bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent mr-2"></i>Achievements Earned
<h3 class="text-lg font-semibold text-white">
<i class="fas fa-award bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent mr-2"></i>Achievements Earned
</h3>
<span class="px-2.5 py-1 rounded-full bg-gradient-to-r from-yellow-400 to-amber-500 text-white text-sm font-bold shadow-md">
{{ contributor.achievements.length }}
@@ -401,7 +401,7 @@ watch(globalData, loadContributor)
<div
v-for="achievement in contributor.achievements"
:key="achievement"
class="flex flex-col items-center p-2 rounded-xl bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
class="flex flex-col items-center p-2 rounded-xl bg-gray-800/50 hover:bg-gray-800 transition-colors"
>
<AchievementBadge
:achievement-id="achievement"
@@ -414,7 +414,7 @@ watch(globalData, loadContributor)
<!-- Progress to Next Achievements -->
<Card>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-6">
<h3 class="text-lg font-semibold text-white mb-6">
<i class="fas fa-chart-line text-primary-500 mr-2"></i>Next Achievements
</h3>
@@ -441,9 +441,9 @@ watch(globalData, loadContributor)
v-for="repo in contributor.repositories_contributed"
:key="repo"
:to="`/repos/${repo}`"
class="inline-flex items-center px-3 py-1.5 rounded-full text-sm bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-primary-100 dark:hover:bg-primary-900/30 hover:text-primary-700 dark:hover:text-primary-300 transition-colors"
class="inline-flex items-center px-3 py-1.5 rounded-full text-sm bg-gray-800 text-gray-300 hover:bg-primary-900/30 hover:text-primary-300 transition-colors"
>
<i class="fas fa-code-branch text-gray-600 dark:text-gray-400 mr-2"></i>
<i class="fas fa-code-branch text-gray-400 mr-2"></i>
{{ repo }}
</RouterLink>
</div>
+5 -5
View File
@@ -27,13 +27,13 @@ const showScoreInChart = ref(false)
<header class="py-10 sm:py-16 px-4">
<div class="container mx-auto text-center animate-[fadeInUp_0.6s_ease-out]">
<h1 class="text-3xl sm:text-4xl md:text-6xl font-bold mb-3 sm:mb-4">
<span class="bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">Git Velocity</span>
<span class="bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">Git Velocity</span>
</h1>
<p class="text-base sm:text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto px-2">
<p class="text-base sm:text-xl text-gray-300 max-w-2xl mx-auto px-2">
Celebrate your team's achievements and contributions with beautiful insights.
</p>
<!-- Period and Generation Info -->
<div class="flex flex-col items-center space-y-2 mt-4 text-sm text-gray-600 dark:text-gray-400">
<div class="flex flex-col items-center space-y-2 mt-4 text-sm text-gray-400">
<p v-if="metrics.period?.start || metrics.period?.end">
<i class="fas fa-calendar-alt mr-1 text-primary-500"></i>
<span class="font-medium">Period:</span>
@@ -55,11 +55,11 @@ const showScoreInChart = ref(false)
<Card>
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4 sm:mb-6">
<SectionHeader title="Velocity Timeline" icon="fas fa-chart-line" icon-color="text-primary-500" />
<label class="flex items-center space-x-2 text-sm text-gray-700 dark:text-gray-400 cursor-pointer">
<label class="flex items-center space-x-2 text-sm text-gray-400 cursor-pointer">
<input
type="checkbox"
v-model="showScoreInChart"
class="rounded border-gray-300 dark:border-gray-600 text-primary-500 focus:ring-primary-500"
class="rounded border-gray-600 text-primary-500 focus:ring-primary-500"
/>
<span>Show Score</span>
</label>
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -67,17 +67,17 @@ const categoryIcon = (category) => {
v-model="searchQuery"
type="text"
placeholder="Search contributors..."
class="w-full pl-10 pr-10 py-2.5 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition text-sm sm:text-base"
class="w-full pl-10 pr-10 py-2.5 rounded-lg border border-gray-700 bg-gray-800 text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition text-sm sm:text-base"
/>
<button
v-if="searchQuery"
@click="searchQuery = ''"
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-200"
>
<i class="fas fa-times"></i>
</button>
</div>
<p v-if="searchQuery && leaderboard.length !== allContributors.length" class="mt-2 text-sm text-gray-600 dark:text-gray-400">
<p v-if="searchQuery && leaderboard.length !== allContributors.length" class="mt-2 text-sm text-gray-400">
Showing {{ leaderboard.length }} of {{ allContributors.length }} contributors
</p>
</div>
@@ -100,25 +100,25 @@ const categoryIcon = (category) => {
<!-- Info -->
<div class="flex-1 min-w-0">
<div class="font-semibold text-gray-900 dark:text-white truncate">
<div class="font-semibold text-white truncate">
{{ item.name || item.login }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">
<div class="text-xs text-gray-400 truncate">
@{{ item.login }}
</div>
</div>
<!-- Score -->
<div class="text-right">
<div class="text-lg font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
<div class="text-lg font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
{{ formatNumber(item.score) }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">pts</div>
<div class="text-xs text-gray-400">pts</div>
</div>
</div>
<!-- Achievements row -->
<div v-if="item.achievements?.length" class="mt-3 pt-3 border-t border-gray-100 dark:border-gray-700">
<div v-if="item.achievements?.length" class="mt-3 pt-3 border-t border-gray-700">
<div class="flex flex-wrap gap-1.5">
<AchievementBadge
v-for="achievement in getHighestTierAchievements(item.achievements).slice(0, 6)"
@@ -128,7 +128,7 @@ const categoryIcon = (category) => {
/>
<span
v-if="getHighestTierAchievements(item.achievements).length > 6"
class="inline-flex items-center justify-center px-2 h-7 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-bold"
class="inline-flex items-center justify-center px-2 h-7 rounded-lg bg-gray-700 text-gray-300 text-xs font-bold"
>
+{{ getHighestTierAchievements(item.achievements).length - 6 }}
</span>
@@ -139,8 +139,8 @@ const categoryIcon = (category) => {
<!-- Empty State -->
<div v-if="!leaderboard.length" class="text-center py-12">
<i class="fas fa-users text-4xl text-gray-400 dark:text-gray-600 mb-4"></i>
<p class="text-gray-600 dark:text-gray-400">No contributors found</p>
<i class="fas fa-users text-4xl text-gray-500 mb-4"></i>
<p class="text-gray-400">No contributors found</p>
</div>
</div>
@@ -151,7 +151,7 @@ const categoryIcon = (category) => {
:items="leaderboard"
empty-icon="fas fa-users"
empty-message="No contributors found"
row-class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition group"
row-class="hover:bg-gray-800/30 transition group"
>
<template #rank="{ item }">
<RankBadge :rank="item.rank" />
@@ -170,7 +170,7 @@ const categoryIcon = (category) => {
:achievement-id="achievement"
size="sm"
/>
<span v-if="!(item.achievements || []).length" class="text-gray-600 dark:text-gray-400 text-sm">-</span>
<span v-if="!(item.achievements || []).length" class="text-gray-400 text-sm">-</span>
</div>
</td>
</template>
@@ -179,16 +179,16 @@ const categoryIcon = (category) => {
<td class="hidden xl:table-cell">
<span
v-if="item.team"
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300"
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-900/30 text-purple-300"
>
{{ item.team }}
</span>
<span v-else class="text-gray-600 dark:text-gray-400">-</span>
<span v-else class="text-gray-400">-</span>
</td>
</template>
<template #score="{ item }">
<span class="text-lg font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
<span class="text-lg font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
{{ formatNumber(item.score) }}
</span>
</template>
+9 -9
View File
@@ -128,19 +128,19 @@ watch(() => route.params, loadRepository)
v-model="searchQuery"
type="text"
placeholder="Search contributors..."
class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition text-sm"
class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-700 bg-gray-800 text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition text-sm"
/>
<button
v-if="searchQuery"
@click="searchQuery = ''"
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-200"
>
<i class="fas fa-times"></i>
</button>
</div>
</div>
<p v-if="searchQuery && filteredContributors.length !== allContributors.length" class="mb-4 text-sm text-gray-600 dark:text-gray-400">
<p v-if="searchQuery && filteredContributors.length !== allContributors.length" class="mb-4 text-sm text-gray-400">
Showing {{ filteredContributors.length }} of {{ allContributors.length }} contributors
</p>
@@ -149,27 +149,27 @@ watch(() => route.params, loadRepository)
:items="filteredContributors"
empty-icon="fas fa-users"
empty-message="No contributors found"
row-class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition group"
row-class="hover:bg-gray-800/30 transition group"
>
<template #contributor="{ item }">
<ContributorRow :contributor="item" />
</template>
<template #commits="{ item }">
<span class="text-gray-900 dark:text-white">{{ formatNumber(item.commit_count) }}</span>
<span class="text-white">{{ formatNumber(item.commit_count) }}</span>
</template>
<template #prs="{ item }">
<span class="text-gray-900 dark:text-white">{{ formatNumber(item.prs_opened) }}</span>
<span class="text-white">{{ formatNumber(item.prs_opened) }}</span>
</template>
<template #reviews="{ item }">
<span class="text-gray-900 dark:text-white">{{ formatNumber(item.reviews_given) }}</span>
<span class="text-white">{{ formatNumber(item.reviews_given) }}</span>
</template>
<template #lines="{ item }">
<span class="text-green-500">+{{ formatNumber(item.lines_added) }}</span>
<span class="text-gray-600 dark:text-gray-400 mx-1">/</span>
<span class="text-gray-400 mx-1">/</span>
<span class="text-red-500">-{{ formatNumber(item.lines_deleted) }}</span>
</template>
<template #score="{ item }">
<span class="text-lg font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
<span class="text-lg font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
{{ formatNumber(item.score?.total || 0) }}
</span>
</template>