Fix styling

This commit is contained in:
2025-12-12 22:41:21 +00:00
parent a5b522c996
commit a5d69ccb86
29 changed files with 610 additions and 542 deletions
+10 -10
View File
@@ -266,12 +266,12 @@ const remainingCount = computed(() => {
<i class="fas text-white text-sm" :class="item.icon"></i>
</div>
<div>
<div class="text-sm font-semibold text-gray-800 dark:text-white">
<div class="text-sm font-semibold text-gray-900 dark:text-white">
{{ item.name }}
</div>
<div class="flex items-center space-x-2 text-xs text-gray-500 dark:text-gray-400">
<div class="flex items-center space-x-2 text-xs text-gray-600 dark:text-gray-400">
<span>{{ item.category }}</span>
<span class="text-gray-300 dark:text-gray-600"></span>
<span class="text-gray-600 dark:text-gray-400"></span>
<span class="font-medium">Tier {{ item.tierIndex }}/{{ item.totalTiers }}</span>
</div>
</div>
@@ -279,10 +279,10 @@ const remainingCount = computed(() => {
<div class="text-right">
<div class="text-sm font-bold" :class="item.isClose ? 'text-green-500' : 'text-gray-700 dark:text-gray-200'">
{{ formatNumber(item.currentValue) }}
<span class="text-gray-400 dark:text-gray-500 font-normal">/</span>
<span class="text-gray-500 dark:text-gray-400 font-medium">{{ formatNumber(item.target) }}</span>
<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>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
<div class="text-xs text-gray-600 dark:text-gray-400 mt-0.5">
{{ item.remaining > 0 ? `${formatNumber(item.remaining)} to go` : 'Ready to claim!' }}
</div>
</div>
@@ -307,11 +307,11 @@ const remainingCount = computed(() => {
:class="idx < item.tierIndex ? 'bg-green-500' : 'bg-gray-300 dark:bg-gray-600'"
:title="`Tier ${idx + 1}: ${t.name} (${formatNumber(t.threshold)})`"
></span>
<span v-if="item.totalTiers > 5" class="text-[10px] text-gray-400">+{{ item.totalTiers - 5 }}</span>
<span v-if="item.totalTiers > 5" class="text-[10px] text-gray-600 dark:text-gray-400">+{{ item.totalTiers - 5 }}</span>
</div>
<span
class="text-xs font-semibold"
:class="item.isClose ? 'text-green-500' : 'text-gray-400 dark:text-gray-500'"
:class="item.isClose ? 'text-green-500' : 'text-gray-600 dark:text-gray-400'"
>
{{ item.progress }}%
</span>
@@ -319,12 +319,12 @@ const remainingCount = computed(() => {
</div>
<!-- Show more indicator -->
<div v-if="remainingCount > 0" class="text-center text-xs text-gray-500 dark:text-gray-400 pt-2">
<div v-if="remainingCount > 0" class="text-center text-xs text-gray-600 dark: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-500 dark:text-gray-400">
<div v-if="!progressItems.length" class="text-center py-8 text-gray-600 dark: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>
+2 -2
View File
@@ -11,7 +11,7 @@ defineProps({
</script>
<template>
<div class="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400 mb-6">
<div class="flex items-center space-x-2 text-sm text-gray-600 dark: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-800 dark:text-white' : ''"
:class="index === items.length - 1 ? 'text-gray-900 dark:text-white' : ''"
>
{{ item.label }}
</span>
+18
View File
@@ -0,0 +1,18 @@
<script setup>
defineProps({
padding: { type: Boolean, default: true },
hover: { type: Boolean, default: false }
})
</script>
<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',
padding ? 'p-6' : '',
hover ? 'hover:shadow-lg transition-shadow' : ''
]"
>
<slot />
</div>
</template>
+67 -55
View File
@@ -1,5 +1,6 @@
<script setup>
import { RouterLink } from 'vue-router'
import Card from './Card.vue'
import Avatar from './Avatar.vue'
import RankBadge from './RankBadge.vue'
import AchievementBadge from './AchievementBadge.vue'
@@ -16,63 +17,74 @@ defineProps({
<template>
<RouterLink
:to="{ name: 'contributor', params: { login: contributor.login } }"
:class="[
'card animate-fade-in-up block cursor-pointer hover:shadow-lg transition-shadow',
featured && rank === 1 ? 'ring-2 ring-yellow-400' : ''
]"
class="block group"
>
<div class="flex items-center space-x-4">
<div class="relative">
<Avatar
:src="contributor.avatar_url"
:name="contributor.login"
:size="featured ? 'xl' : 'lg'"
/>
<RankBadge
v-if="showRank && rank > 0"
:rank="rank"
size="sm"
class="absolute -top-1 -right-1"
/>
</div>
<div class="flex-1">
<h3 class="font-semibold text-gray-800 dark:text-white group-hover:text-primary-500 transition-colors">
{{ contributor.name || contributor.login }}
</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">
<span
class="hover:text-primary-500 transition-colors"
@click.stop.prevent="window.open(`https://github.com/${contributor.login}`, '_blank')"
>
@{{ contributor.login }}
<i class="fas fa-external-link-alt text-xs ml-0.5 opacity-50"></i>
</span>
</p>
<p v-if="contributor.team" class="text-xs text-accent-500">{{ contributor.team }}</p>
</div>
<div class="text-right">
<div class="text-2xl font-bold gradient-text">
{{ formatNumber(contributor.score?.total || contributor.score || 0) }}
<Card
hover
:class="[
'animate-[fadeInUp_0.6s_ease-out] h-full',
featured && rank === 1 ? 'ring-2 ring-yellow-400' : '',
featured && rank === 2 ? 'ring-2 ring-gray-300' : '',
featured && rank === 3 ? 'ring-2 ring-amber-600' : ''
]"
>
<div class="flex flex-col h-full">
<!-- Header with avatar and rank -->
<div class="flex items-start justify-between mb-4">
<div class="flex items-center gap-4">
<div class="relative">
<Avatar
:src="contributor.avatar_url"
:name="contributor.login"
:size="featured ? 'xl' : 'lg'"
class="ring-2 ring-gray-100 dark:ring-gray-700"
/>
<RankBadge
v-if="showRank && rank > 0"
:rank="rank"
size="sm"
class="absolute -bottom-1 -right-1"
/>
</div>
<div>
<h3 class="font-bold text-lg text-gray-900 dark: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">
@{{ contributor.login }}
</p>
<p v-if="contributor.team" class="text-xs text-accent-500 mt-0.5">{{ contributor.team }}</p>
</div>
</div>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">points</div>
</div>
</div>
<div v-if="contributor.achievements?.length" class="mt-4 flex flex-wrap gap-1.5">
<AchievementBadge
v-for="achievement in contributor.achievements.slice(0, 6)"
:key="achievement"
:achievement-id="achievement"
size="sm"
/>
<span
v-if="contributor.achievements.length > 6"
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"
>
+{{ contributor.achievements.length - 6 }}
</span>
</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">
{{ 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="flex flex-wrap gap-1.5">
<AchievementBadge
v-for="achievement in contributor.achievements.slice(0, 8)"
:key="achievement"
:achievement-id="achievement"
size="sm"
/>
<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"
>
+{{ contributor.achievements.length - 8 }}
</span>
</div>
</div>
</div>
</Card>
</RouterLink>
</template>
+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-800 dark:text-white group-hover:text-primary-500 transition-colors">
<div class="font-medium text-gray-900 dark: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="text-gray-500 dark:text-gray-400 hover:text-primary-500 transition-colors"
class="font-medium text-gray-800 dark: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="text-gray-500 dark:text-gray-400">
<span v-else class="font-medium text-gray-800 dark:text-gray-400">
@{{ contributor.login }}
</span>
</div>
+7 -5
View File
@@ -1,4 +1,6 @@
<script setup>
import Card from './Card.vue'
defineProps({
columns: {
type: Array,
@@ -39,7 +41,7 @@ const getAlignClass = (align) => {
</script>
<template>
<div class="card overflow-hidden p-0">
<Card :padding="false" class="overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 dark:bg-gray-800/50">
<tr>
@@ -47,7 +49,7 @@ const getAlignClass = (align) => {
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-500 dark:text-gray-400 uppercase tracking-wider',
'px-3 sm:px-6 py-3 sm:py-4 text-xs font-semibold text-gray-700 dark:text-gray-400 uppercase tracking-wider',
getAlignClass(col.align),
col.headerClass
]"
@@ -78,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-300 dark:text-gray-600 mb-4"></i>
<p class="text-gray-500 dark:text-gray-400">{{ emptyMessage }}</p>
<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>
</div>
</div>
</Card>
</template>
+3 -3
View File
@@ -16,18 +16,18 @@ const generatedAt = computed(() => {
<template>
<footer class="py-8 px-4 mt-16 border-t border-gray-200 dark:border-gray-700">
<div class="container mx-auto text-center">
<p class="text-gray-500 dark:text-gray-400">
<p class="text-gray-700 dark:text-gray-400">
Generated by
<a
href="https://github.com/lukaszraczylo/git-velocity"
class="text-primary-500 hover:text-primary-600"
class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-medium"
target="_blank"
rel="noopener noreferrer"
>
Git Velocity
</a>
</p>
<p v-if="generatedAt" class="text-sm text-gray-400 dark:text-gray-500 mt-2">
<p v-if="generatedAt" class="text-sm text-gray-600 dark:text-gray-500 mt-2">
{{ generatedAt }}
</p>
</div>
+53 -50
View File
@@ -1,5 +1,6 @@
<script setup>
import { RouterLink } from 'vue-router'
import Card from './Card.vue'
import Avatar from './Avatar.vue'
import AchievementBadge from './AchievementBadge.vue'
import { formatNumber } from '../composables/formatters'
@@ -20,60 +21,62 @@ defineProps({
<component
:is="linkToProfile ? RouterLink : 'div'"
:to="linkToProfile ? { name: 'contributor', params: { login: member.login } } : undefined"
class="card block"
:class="{ 'hover:shadow-lg transition cursor-pointer': linkToProfile }"
class="block"
:class="{ 'group': linkToProfile }"
>
<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-800 dark:text-white">
{{ member.name || member.login }}
</h3>
<p class="text-sm text-gray-500 dark: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-800 dark:text-white">
{{ formatNumber(member.commit_count) }}
<Card :hover="linkToProfile" :class="{ 'cursor-pointer': linkToProfile }">
<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">
{{ member.name || member.login }}
</h3>
<p class="text-sm text-gray-800 dark:text-gray-400">@{{ member.login }}</p>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">Commits</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-800 dark:text-white">
{{ formatNumber(member.prs_opened) }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">PRs</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-800 dark:text-white">
{{ formatNumber(member.reviews_given) }}
</div>
<div class="text-xs text-gray-500 dark: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-500 dark:text-gray-400">Score</span>
<span class="text-xl font-bold gradient-text">
{{ formatNumber(member.score?.total || 0) }}
</span>
</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">
{{ formatNumber(member.commit_count) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Commits</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
{{ formatNumber(member.prs_opened) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">PRs</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
{{ formatNumber(member.reviews_given) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Reviews</div>
</div>
</div>
<div v-if="member.achievements?.length" class="mt-4 flex flex-wrap gap-2">
<AchievementBadge
v-for="achievement in member.achievements.slice(0, 4)"
:key="achievement"
:achievement-id="achievement"
size="sm"
/>
<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"
>
+{{ member.achievements.length - 4 }}
</span>
</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">
{{ formatNumber(member.score?.total || 0) }}
</span>
</div>
<div v-if="member.achievements?.length" class="mt-4 flex flex-wrap gap-2">
<AchievementBadge
v-for="achievement in member.achievements.slice(0, 4)"
:key="achievement"
:achievement-id="achievement"
size="sm"
/>
<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"
>
+{{ member.achievements.length - 4 }}
</span>
</div>
</Card>
</component>
</template>
+7 -7
View File
@@ -11,32 +11,32 @@ const repositories = computed(() => globalData.value?.Repositories || [])
</script>
<template>
<nav class="sticky top-0 z-50 glass shadow-modern">
<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">
<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 gradient-text"></i>
<span class="text-xl font-bold gradient-text">Git Velocity</span>
<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>
</RouterLink>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-6">
<RouterLink
to="/"
:class="route.path === '/' ? 'nav-link-active' : 'nav-link'"
: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'"
>
Dashboard
</RouterLink>
<RouterLink
to="/leaderboard"
:class="route.path === '/leaderboard' ? 'nav-link-active' : 'nav-link'"
: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'"
>
Leaderboard
</RouterLink>
<RouterLink
to="/how-scoring-works"
:class="route.path === '/how-scoring-works' ? 'nav-link-active' : 'nav-link'"
: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'"
>
How Scoring Works
</RouterLink>
@@ -44,7 +44,7 @@ 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}`) ? 'nav-link-active' : 'nav-link'"
: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'"
>
{{ repo.Name }}
</RouterLink>
+1 -1
View File
@@ -38,7 +38,7 @@ 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="gradient-text">{{ title }}</span>
<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>
</h1>
</div>
+9 -5
View File
@@ -8,17 +8,20 @@ const props = defineProps({
const sizeClasses = {
sm: 'w-6 h-6 text-xs',
md: 'w-8 h-8 text-sm'
md: 'w-8 h-8 text-sm',
lg: 'w-10 h-10 text-base'
}
const rankClass = computed(() => {
if (props.rank === 1) return 'rank-1'
if (props.rank === 2) return 'rank-2'
if (props.rank === 3) return 'rank-3'
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'
})
const classes = computed(() => sizeClasses[props.size] || sizeClasses.md)
const isTopThree = computed(() => props.rank >= 1 && props.rank <= 3)
</script>
<template>
@@ -26,6 +29,7 @@ const classes = computed(() => sizeClasses[props.size] || sizeClasses.md)
:class="[classes, rankClass, { 'text-white': rank <= 3 }]"
class="inline-flex items-center justify-center rounded-full font-bold"
>
{{ rank }}
<i v-if="isTopThree" class="fas fa-trophy"></i>
<template v-else>{{ rank }}</template>
</span>
</template>
+27 -24
View File
@@ -1,5 +1,6 @@
<script setup>
import { RouterLink } from 'vue-router'
import Card from './Card.vue'
import { formatNumber } from '../composables/formatters'
defineProps({
@@ -13,35 +14,37 @@ defineProps({
<template>
<RouterLink
:to="`/repos/${repo.owner}/${repo.name}`"
class="card hover:shadow-lg transition group"
class="block group"
>
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-gray-800 dark:text-white group-hover:text-primary-500 transition">
{{ repo.name }}
</h3>
<i class="fas fa-arrow-right text-gray-400 group-hover:text-primary-500 transition"></i>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">{{ repo.owner }}/{{ repo.name }}</p>
<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">
{{ repo.name }}
</h3>
<i class="fas fa-arrow-right text-gray-600 dark: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>
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<div class="text-lg font-semibold text-gray-800 dark:text-white">
{{ formatNumber(repo.total_commits) }}
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
{{ formatNumber(repo.total_commits) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Commits</div>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">Commits</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-800 dark:text-white">
{{ formatNumber(repo.total_prs) }}
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
{{ formatNumber(repo.total_prs) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">PRs</div>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">PRs</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-800 dark:text-white">
{{ repo.active_contributors }}
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
{{ repo.active_contributors }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Contributors</div>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">Contributors</div>
</div>
</div>
</Card>
</RouterLink>
</template>
+1 -1
View File
@@ -16,7 +16,7 @@ defineProps({
</script>
<template>
<h2 class="text-2xl font-bold text-gray-800 dark:text-white mb-6">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">
<i v-if="icon" :class="[icon, iconColor]" class="mr-2"></i>{{ title }}
<slot name="suffix"></slot>
</h2>
+5 -4
View File
@@ -1,4 +1,5 @@
<script setup>
import Card from './Card.vue'
import { formatNumber } from '../composables/formatters'
defineProps({
@@ -6,23 +7,23 @@ defineProps({
label: { type: String, required: true },
icon: { type: String, default: '' },
iconColor: { type: String, default: 'text-gray-500' },
valueClass: { type: String, default: 'gradient-text' },
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' },
delay: { type: String, default: '0s' }
})
</script>
<template>
<div class="card animate-fade-in-up" :style="{ animationDelay: delay }">
<Card class="animate-[fadeInUp_0.6s_ease-out]" :style="{ animationDelay: delay }">
<div class="flex items-center justify-between">
<div class="min-w-0 flex-1">
<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-500 dark:text-gray-400 mt-1 truncate">{{ label }}</div>
<div class="text-xs sm:text-sm text-gray-700 dark: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>
</div>
</div>
</div>
</Card>
</template>
+38 -35
View File
@@ -1,5 +1,6 @@
<script setup>
import { RouterLink } from 'vue-router'
import Card from './Card.vue'
import Avatar from './Avatar.vue'
import { formatNumber, slugify } from '../composables/formatters'
@@ -14,43 +15,45 @@ defineProps({
<template>
<RouterLink
:to="`/teams/${slugify(team.name)}`"
class="card hover:shadow-lg transition group"
class="block group"
>
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-gray-800 dark:text-white group-hover:text-primary-500 transition">
{{ team.name }}
</h3>
<span
class="w-3 h-3 rounded-full"
:style="{ backgroundColor: team.color || '#8b5cf6' }"
></span>
</div>
<div class="flex items-center space-x-2 mb-4">
<template v-for="(member, i) in (team.members || []).slice(0, 5)" :key="member">
<Avatar :name="member" size="sm" />
</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"
>
+{{ team.members.length - 5 }}
</span>
</div>
<div class="grid grid-cols-2 gap-4 text-center">
<div>
<div class="text-lg font-semibold gradient-text">
{{ formatNumber(team.total_score) }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">Total Score</div>
<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">
{{ team.name }}
</h3>
<span
class="w-3 h-3 rounded-full"
:style="{ backgroundColor: team.color || '#8b5cf6' }"
></span>
</div>
<div>
<div class="text-lg font-semibold text-gray-800 dark:text-white">
{{ team.members?.length || 0 }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">Members</div>
<div class="flex items-center space-x-2 mb-4">
<template v-for="(member, i) in (team.members || []).slice(0, 5)" :key="member">
<Avatar :name="member" size="sm" />
</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"
>
+{{ team.members.length - 5 }}
</span>
</div>
</div>
<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">
{{ formatNumber(team.total_score) }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Total Score</div>
</div>
<div>
<div class="text-lg font-semibold text-gray-900 dark:text-white">
{{ team.members?.length || 0 }}
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">Members</div>
</div>
</div>
</Card>
</RouterLink>
</template>
+1 -1
View File
@@ -177,7 +177,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-400">No velocity data available</p>
<p class="text-gray-500 dark:text-gray-400">No velocity data available</p>
</div>
</div>
</template>