Improve mobile responsiveness, explain scoring, simple search.

This commit is contained in:
2025-12-12 20:42:58 +00:00
parent 09b0c533b4
commit a5b522c996
14 changed files with 1007 additions and 41 deletions
+2 -2
View File
@@ -47,7 +47,7 @@ const getAlignClass = (align) => {
v-for="col in columns"
:key="col.key"
:class="[
'px-6 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-500 dark:text-gray-400 uppercase tracking-wider',
getAlignClass(col.align),
col.headerClass
]"
@@ -66,7 +66,7 @@ const getAlignClass = (align) => {
<td
v-for="col in columns"
:key="col.key"
:class="['px-6 py-4', getAlignClass(col.align), col.class]"
:class="['px-3 sm:px-6 py-3 sm:py-4', getAlignClass(col.align), col.class]"
>
<slot :name="col.key" :item="item" :index="index">
{{ item[col.key] }}
+41 -8
View File
@@ -34,6 +34,12 @@ const repositories = computed(() => globalData.value?.Repositories || [])
>
Leaderboard
</RouterLink>
<RouterLink
to="/how-scoring-works"
:class="route.path === '/how-scoring-works' ? 'nav-link-active' : 'nav-link'"
>
How Scoring Works
</RouterLink>
<RouterLink
v-for="repo in repositories"
:key="`${repo.Owner}/${repo.Name}`"
@@ -58,30 +64,57 @@ const repositories = computed(() => globalData.value?.Repositories || [])
</div>
<!-- Mobile Menu -->
<div v-if="mobileMenuOpen" class="md:hidden py-4 border-t border-gray-200 dark:border-gray-700">
<div class="flex flex-col space-y-3">
<div v-if="mobileMenuOpen" class="md:hidden py-2 border-t border-gray-200 dark:border-gray-700">
<div class="flex flex-col space-y-1">
<RouterLink
to="/"
@click="mobileMenuOpen = false"
:class="route.path === '/' ? 'nav-link-active' : 'nav-link'"
: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'
]"
>
Dashboard
<i class="fas fa-home mr-3 w-5 text-center"></i>Dashboard
</RouterLink>
<RouterLink
to="/leaderboard"
@click="mobileMenuOpen = false"
:class="route.path === '/leaderboard' ? 'nav-link-active' : 'nav-link'"
: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'
]"
>
Leaderboard
<i class="fas fa-trophy mr-3 w-5 text-center"></i>Leaderboard
</RouterLink>
<RouterLink
to="/how-scoring-works"
@click="mobileMenuOpen = false"
: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'
]"
>
<i class="fas fa-calculator mr-3 w-5 text-center"></i>How Scoring Works
</RouterLink>
<RouterLink
v-for="repo in repositories"
:key="`${repo.Owner}/${repo.Name}`"
:to="`/repos/${repo.Owner}/${repo.Name}`"
@click="mobileMenuOpen = false"
:class="route.path.includes(`/repos/${repo.Owner}/${repo.Name}`) ? 'nav-link-active' : 'nav-link'"
: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'
]"
>
{{ repo.Name }}
<i class="fas fa-code-branch mr-3 w-5 text-center"></i>{{ repo.Name }}
</RouterLink>
</div>
</div>
+4 -4
View File
@@ -14,13 +14,13 @@ defineProps({
<template>
<div class="card animate-fade-in-up" :style="{ animationDelay: delay }">
<div class="flex items-center justify-between">
<div>
<div class="text-3xl font-bold" :class="valueClass">
<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-sm text-gray-500 dark:text-gray-400 mt-1">{{ label }}</div>
<div class="text-xs sm:text-sm text-gray-500 dark:text-gray-400 mt-1 truncate">{{ label }}</div>
</div>
<div v-if="icon" class="text-3xl opacity-50" :class="iconColor">
<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>
+34 -12
View File
@@ -1,5 +1,5 @@
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { Chart, registerables } from 'chart.js'
Chart.register(...registerables)
@@ -49,7 +49,9 @@ const chartData = computed(() => {
}
})
const chartOptions = {
const isMobile = ref(window.innerWidth < 640)
const chartOptions = computed(() => ({
responsive: true,
maintainAspectRatio: false,
interaction: {
@@ -61,20 +63,21 @@ const chartOptions = {
position: 'top',
labels: {
usePointStyle: true,
padding: 20,
padding: isMobile.value ? 10 : 20,
boxWidth: isMobile.value ? 8 : 12,
font: {
size: 12
size: isMobile.value ? 10 : 12
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
padding: isMobile.value ? 8 : 12,
titleFont: {
size: 14
size: isMobile.value ? 12 : 14
},
bodyFont: {
size: 13
size: isMobile.value ? 11 : 13
},
callbacks: {
label: (context) => {
@@ -90,8 +93,11 @@ const chartOptions = {
},
ticks: {
font: {
size: 11
}
size: isMobile.value ? 9 : 11
},
maxRotation: isMobile.value ? 45 : 0,
autoSkip: true,
maxTicksLimit: isMobile.value ? 6 : 12
}
},
y: {
@@ -101,7 +107,7 @@ const chartOptions = {
},
ticks: {
font: {
size: 11
size: isMobile.value ? 9 : 11
},
callback: (value) => {
if (value >= 1000) {
@@ -112,7 +118,7 @@ const chartOptions = {
}
}
}
}
}))
function createChart() {
if (!chartRef.value || !chartData.value.labels.length) return
@@ -125,7 +131,7 @@ function createChart() {
chartInstance = new Chart(ctx, {
type: 'line',
data: chartData.value,
options: chartOptions
options: chartOptions.value
})
}
@@ -138,8 +144,24 @@ function updateChart() {
}
}
function handleResize() {
const newIsMobile = window.innerWidth < 640
if (newIsMobile !== isMobile.value) {
isMobile.value = newIsMobile
createChart() // Recreate chart with new options
}
}
onMounted(() => {
createChart()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance) {
chartInstance.destroy()
}
})
watch(() => props.timeline, () => {