@commonpub/layer 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/pages/contests/index.vue +75 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"zod": "^4.3.6",
|
|
56
56
|
"@commonpub/auth": "0.8.0",
|
|
57
57
|
"@commonpub/config": "0.18.0",
|
|
58
|
-
"@commonpub/
|
|
58
|
+
"@commonpub/learning": "0.5.2",
|
|
59
59
|
"@commonpub/docs": "0.6.3",
|
|
60
60
|
"@commonpub/explainer": "0.7.15",
|
|
61
|
-
"@commonpub/
|
|
61
|
+
"@commonpub/editor": "0.7.11",
|
|
62
62
|
"@commonpub/protocol": "0.13.0",
|
|
63
63
|
"@commonpub/schema": "0.26.0",
|
|
64
64
|
"@commonpub/server": "2.73.0",
|
package/pages/contests/index.vue
CHANGED
|
@@ -13,6 +13,20 @@ function cardBlurb(c: { subheading?: string | null; description?: string | null
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const config = useRuntimeConfig();
|
|
16
|
+
|
|
17
|
+
// Contest banner thumbnail — proxy cross-origin images through our server
|
|
18
|
+
// (same pattern as ContentCard) for caching + faster loads.
|
|
19
|
+
function coverFor(url: string | null | undefined): string | null {
|
|
20
|
+
if (!url) return null;
|
|
21
|
+
const siteDomain = (config.public?.domain as string) || '';
|
|
22
|
+
try {
|
|
23
|
+
if (siteDomain && !url.includes(siteDomain)) {
|
|
24
|
+
return `/api/image-proxy?url=${encodeURIComponent(url)}&w=600`;
|
|
25
|
+
}
|
|
26
|
+
} catch { /* invalid URL — use as-is */ }
|
|
27
|
+
return url;
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
const contestCreation = config.public.contestCreation as string || 'admin';
|
|
17
31
|
const canCreateContest = computed(() => {
|
|
18
32
|
if (!isAuthenticated.value) return false;
|
|
@@ -31,30 +45,45 @@ const canCreateContest = computed(() => {
|
|
|
31
45
|
</NuxtLink>
|
|
32
46
|
</div>
|
|
33
47
|
<div v-if="contests?.items?.length" class="cpub-grid-3">
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
<NuxtLink
|
|
49
|
+
v-for="contest in contests.items"
|
|
50
|
+
:key="contest.id"
|
|
51
|
+
:to="`/contests/${contest.slug}`"
|
|
52
|
+
class="cpub-card cpub-contest-card"
|
|
53
|
+
>
|
|
54
|
+
<!-- Banner thumbnail (contest.bannerUrl) with trophy fallback + status badge overlay -->
|
|
55
|
+
<div class="cpub-contest-thumb">
|
|
56
|
+
<img
|
|
57
|
+
v-if="coverFor(contest.bannerUrl)"
|
|
58
|
+
:src="coverFor(contest.bannerUrl)!"
|
|
59
|
+
:alt="contest.title"
|
|
60
|
+
class="cpub-contest-cover"
|
|
61
|
+
loading="lazy"
|
|
62
|
+
/>
|
|
63
|
+
<template v-else>
|
|
64
|
+
<div class="cpub-contest-thumb-grid" />
|
|
65
|
+
<i class="fa-solid fa-trophy cpub-contest-thumb-icon" />
|
|
66
|
+
</template>
|
|
67
|
+
<span class="cpub-badge cpub-contest-thumb-badge" :class="{
|
|
37
68
|
'cpub-badge-green': contest.status === 'active',
|
|
38
69
|
'cpub-badge-yellow': contest.status === 'upcoming',
|
|
39
70
|
'cpub-badge-accent': contest.status === 'judging',
|
|
40
71
|
'cpub-badge-red': contest.status === 'completed' || contest.status === 'cancelled',
|
|
41
72
|
}">{{ contest.status }}</span>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</h3>
|
|
47
|
-
<p v-if="cardBlurb(contest)" class="cpub-contest-card-blurb" style="font-size: 12px; color: var(--text-dim); margin-bottom: 12px">
|
|
73
|
+
</div>
|
|
74
|
+
<div class="cpub-card-body">
|
|
75
|
+
<h3 class="cpub-contest-card-title">{{ contest.title }}</h3>
|
|
76
|
+
<p v-if="cardBlurb(contest)" class="cpub-contest-card-blurb">
|
|
48
77
|
{{ cardBlurb(contest) }}
|
|
49
78
|
</p>
|
|
50
79
|
<div v-if="contest.endDate" style="margin-top: 8px">
|
|
51
80
|
<CountdownTimer :target-date="contest.endDate" />
|
|
52
81
|
</div>
|
|
53
|
-
<div
|
|
82
|
+
<div class="cpub-contest-card-meta">
|
|
54
83
|
<span><i class="fa-solid fa-users"></i> {{ contest.entryCount }} entries</span>
|
|
55
84
|
</div>
|
|
56
85
|
</div>
|
|
57
|
-
</
|
|
86
|
+
</NuxtLink>
|
|
58
87
|
</div>
|
|
59
88
|
<div v-else class="cpub-empty-state">
|
|
60
89
|
<div class="cpub-empty-state-icon"><i class="fa-solid fa-trophy"></i></div>
|
|
@@ -71,7 +100,42 @@ const canCreateContest = computed(() => {
|
|
|
71
100
|
.cpub-card:hover { box-shadow: var(--shadow-lg); transform: translate(-1px, -1px); }
|
|
72
101
|
.cpub-card-body { padding: 16px; }
|
|
73
102
|
|
|
103
|
+
/* Whole card is a link */
|
|
104
|
+
.cpub-contest-card { display: block; text-decoration: none; color: inherit; }
|
|
105
|
+
|
|
106
|
+
/* Banner thumbnail — wide (banner-shaped), cover-cropped, with a grid+trophy
|
|
107
|
+
fallback when a contest has no bannerUrl. */
|
|
108
|
+
.cpub-contest-thumb {
|
|
109
|
+
position: relative;
|
|
110
|
+
aspect-ratio: 16 / 9;
|
|
111
|
+
background: var(--surface2);
|
|
112
|
+
border-bottom: var(--border-width-default) solid var(--border);
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
overflow: hidden;
|
|
117
|
+
}
|
|
118
|
+
.cpub-contest-cover { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
119
|
+
.cpub-contest-thumb-grid {
|
|
120
|
+
position: absolute;
|
|
121
|
+
inset: 0;
|
|
122
|
+
background-image:
|
|
123
|
+
linear-gradient(var(--border2) 1px, transparent 1px),
|
|
124
|
+
linear-gradient(90deg, var(--border2) 1px, transparent 1px);
|
|
125
|
+
background-size: 20px 20px;
|
|
126
|
+
opacity: 0.25;
|
|
127
|
+
}
|
|
128
|
+
.cpub-contest-thumb-icon { position: relative; z-index: 1; font-size: 36px; color: var(--accent); opacity: 0.45; }
|
|
129
|
+
.cpub-contest-thumb-badge { position: absolute; top: 10px; left: 10px; z-index: 2; box-shadow: var(--shadow-sm); }
|
|
130
|
+
.cpub-contest-card:hover .cpub-contest-cover { opacity: 0.92; }
|
|
131
|
+
|
|
132
|
+
.cpub-contest-card-title { font-size: 15px; font-weight: 600; margin: 0 0 6px; color: var(--text); }
|
|
133
|
+
.cpub-contest-card-meta { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 11px; color: var(--text-faint); font-family: var(--font-mono); }
|
|
134
|
+
|
|
74
135
|
.cpub-contest-card-blurb {
|
|
136
|
+
font-size: 12px;
|
|
137
|
+
color: var(--text-dim);
|
|
138
|
+
margin-bottom: 12px;
|
|
75
139
|
display: -webkit-box;
|
|
76
140
|
-webkit-line-clamp: 3;
|
|
77
141
|
line-clamp: 3;
|