@markuxt/markuxt 0.1.4

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.
Files changed (94) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +168 -0
  3. package/app.config.d.ts +33 -0
  4. package/nuxt.config.ts +170 -0
  5. package/package.json +43 -0
  6. package/src/components/AppFooter.vue +225 -0
  7. package/src/components/AppHeader.vue +342 -0
  8. package/src/components/Hero.vue +438 -0
  9. package/src/components/Icon.vue +131 -0
  10. package/src/components/LanguageSwitcher.vue +71 -0
  11. package/src/components/MemberCard.vue +198 -0
  12. package/src/components/MembersGrid.vue +129 -0
  13. package/src/components/MermaidDiagram.vue +99 -0
  14. package/src/components/NewsCard.vue +119 -0
  15. package/src/components/PublicationCard.vue +245 -0
  16. package/src/components/SectionTitle.vue +75 -0
  17. package/src/components/content/ProseImg.vue +29 -0
  18. package/src/components/content/ProsePre.vue +58 -0
  19. package/src/components/content/ProseVideo.vue +45 -0
  20. package/src/composables/resolveContentImage.ts +35 -0
  21. package/src/content-transformers/binary-assets.ts +20 -0
  22. package/src/error.vue +58 -0
  23. package/src/layouts/default.vue +37 -0
  24. package/src/middleware/navigation-guard.ts +22 -0
  25. package/src/pages/index.vue +232 -0
  26. package/src/pages/members/[...slug].vue +542 -0
  27. package/src/pages/members/index.vue +147 -0
  28. package/src/pages/news/[...slug].vue +280 -0
  29. package/src/pages/news/index.vue +102 -0
  30. package/src/pages/positions/[...slug].vue +425 -0
  31. package/src/pages/positions/index.vue +266 -0
  32. package/src/pages/projects/[...slug].vue +441 -0
  33. package/src/pages/projects/index.vue +499 -0
  34. package/src/pages/publications/[...slug].vue +435 -0
  35. package/src/pages/publications/index.vue +145 -0
  36. package/src/plugins/mathml-components.ts +33 -0
  37. package/src/plugins/suppress-warnings.ts +40 -0
  38. package/src/public/_markuxt/components/AppFooter.vue +225 -0
  39. package/src/public/_markuxt/components/AppHeader.vue +342 -0
  40. package/src/public/_markuxt/components/Hero.vue +430 -0
  41. package/src/public/_markuxt/components/Icon.vue +131 -0
  42. package/src/public/_markuxt/components/LanguageSwitcher.vue +71 -0
  43. package/src/public/_markuxt/components/MemberCard.vue +198 -0
  44. package/src/public/_markuxt/components/MembersGrid.vue +129 -0
  45. package/src/public/_markuxt/components/MermaidDiagram.vue +99 -0
  46. package/src/public/_markuxt/components/NewsCard.vue +119 -0
  47. package/src/public/_markuxt/components/PublicationCard.vue +245 -0
  48. package/src/public/_markuxt/components/SectionTitle.vue +75 -0
  49. package/src/public/_markuxt/components/content/ProseImg.vue +29 -0
  50. package/src/public/_markuxt/components/content/ProsePre.vue +58 -0
  51. package/src/public/_markuxt/components/content/ProseVideo.vue +45 -0
  52. package/src/public/_markuxt/composables/resolveContentImage.ts +35 -0
  53. package/src/public/_markuxt/content-transformers/binary-assets.ts +20 -0
  54. package/src/public/_markuxt/error.vue +58 -0
  55. package/src/public/_markuxt/layouts/default.vue +37 -0
  56. package/src/public/_markuxt/middleware/navigation-guard.ts +22 -0
  57. package/src/public/_markuxt/pages/index.vue +232 -0
  58. package/src/public/_markuxt/pages/members/[...slug].vue +542 -0
  59. package/src/public/_markuxt/pages/members/index.vue +147 -0
  60. package/src/public/_markuxt/pages/news/[...slug].vue +280 -0
  61. package/src/public/_markuxt/pages/news/index.vue +102 -0
  62. package/src/public/_markuxt/pages/positions/[...slug].vue +425 -0
  63. package/src/public/_markuxt/pages/positions/index.vue +266 -0
  64. package/src/public/_markuxt/pages/projects/[...slug].vue +441 -0
  65. package/src/public/_markuxt/pages/projects/index.vue +499 -0
  66. package/src/public/_markuxt/pages/publications/[...slug].vue +435 -0
  67. package/src/public/_markuxt/pages/publications/index.vue +145 -0
  68. package/src/public/_markuxt/plugins/mathml-components.ts +33 -0
  69. package/src/public/_markuxt/plugins/suppress-warnings.ts +40 -0
  70. package/src/public/_markuxt/server/plugins/content-locale.ts +47 -0
  71. package/src/public/_markuxt/server/plugins/fix-content-anchors.ts +63 -0
  72. package/src/public/_markuxt/styles/_animations.css +99 -0
  73. package/src/public/_markuxt/styles/_base.css +31 -0
  74. package/src/public/_markuxt/styles/_code.css +109 -0
  75. package/src/public/_markuxt/styles/_components.css +109 -0
  76. package/src/public/_markuxt/styles/_layout.css +220 -0
  77. package/src/public/_markuxt/styles/_markdown.css +52 -0
  78. package/src/public/_markuxt/styles/_tokens.css +62 -0
  79. package/src/public/_markuxt/styles/_typography.css +144 -0
  80. package/src/public/_markuxt/styles/_utilities.css +110 -0
  81. package/src/public/_markuxt/styles/main.css +784 -0
  82. package/src/public/images/logo.png +0 -0
  83. package/src/server/plugins/content-locale.ts +47 -0
  84. package/src/server/plugins/fix-content-anchors.ts +63 -0
  85. package/src/styles/_animations.css +99 -0
  86. package/src/styles/_base.css +31 -0
  87. package/src/styles/_code.css +109 -0
  88. package/src/styles/_components.css +109 -0
  89. package/src/styles/_layout.css +220 -0
  90. package/src/styles/_markdown.css +52 -0
  91. package/src/styles/_tokens.css +62 -0
  92. package/src/styles/_typography.css +144 -0
  93. package/src/styles/_utilities.css +110 -0
  94. package/src/styles/main.css +784 -0
@@ -0,0 +1,441 @@
1
+ <template>
2
+ <main class="project-page" v-if="project">
3
+ <!-- Decorative background -->
4
+ <div class="project__bg">
5
+ <div class="project__pattern"></div>
6
+ <div class="project__shapes">
7
+ <span class="project__shape project__shape--1"></span>
8
+ <span class="project__shape project__shape--2"></span>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="container project-page__container">
13
+ <!-- Back button -->
14
+ <NuxtLink to="/projects" class="project__back">
15
+ <ArrowLeft class="icon-inline" theme="outline" :size="16" fill="currentColor" :stroke-width="2" />
16
+ {{ t('projects.backTo') }}
17
+ </NuxtLink>
18
+
19
+ <!-- Project Header -->
20
+ <div class="project-header animate-fade-in-up">
21
+ <div class="project-header__image-wrapper" v-if="projectImage">
22
+ <img :src="projectImage" :alt="project.title" class="project-header__image" />
23
+ </div>
24
+ <div class="project-header__info">
25
+ <span v-if="project.status" class="project-header__badge" :class="`badge-${project.status}`">
26
+ {{ project.status }}
27
+ </span>
28
+ <h1 class="project-header__name">{{ project.title }}</h1>
29
+ <p v-if="project.year" class="project-header__year">{{ project.year }}</p>
30
+ </div>
31
+ </div>
32
+
33
+ <!-- Project Content -->
34
+ <div class="project-content">
35
+ <!-- Description -->
36
+ <div v-if="project.body || project.description" class="project-section animate-fade-in-up delay-200">
37
+ <div class="project-section__header">
38
+ <FileStaff class="icon-inline" theme="outline" :size="20" fill="white" :stroke-width="2.8" />
39
+ <h3>{{ t('projects.aboutProject') }}</h3>
40
+ </div>
41
+ <div class="project-section__body project-section__body--content">
42
+ <ContentRenderer :value="project" />
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </main>
48
+
49
+ <!-- Not Found -->
50
+ <div v-else class="not-found-page">
51
+ <div class="container">
52
+ <div class="not-found">
53
+ <Help class="icon-inline" theme="outline" :size="80" fill="var(--color-accent)" :stroke-width="3" />
54
+ <h1>{{ t('projects.notFound') }}</h1>
55
+ <p>{{ t('projects.notFoundDesc') }}</p>
56
+ <NuxtLink to="/projects" class="btn btn-primary">{{ t('projects.browseAll') }}</NuxtLink>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </template>
61
+
62
+ <script setup lang="ts">
63
+ import ArrowLeft from '@icon-park/vue-next/es/icons/ArrowLeft'
64
+ import FileStaff from '@icon-park/vue-next/es/icons/FileStaff'
65
+ import Help from '@icon-park/vue-next/es/icons/Help'
66
+
67
+ const { t } = useI18n()
68
+ const route = useRoute()
69
+ const config = useRuntimeConfig()
70
+
71
+ // Get project by file path
72
+ // For catch-all route [...slug], params.slug is an array
73
+ const slug = computed(() => {
74
+ const slugParam = route.params.slug
75
+ return Array.isArray(slugParam) ? slugParam.join('/') : slugParam
76
+ })
77
+
78
+ const { data: projectData } = await useAsyncData(`project-${slug.value}`, async () => {
79
+ try {
80
+ const fullPath = `/projects/${slug.value}`
81
+ return await queryContent(fullPath).findOne()
82
+ } catch (e) {
83
+ console.error('Error fetching project:', e)
84
+ return null
85
+ }
86
+ }, {
87
+ watch: [slug]
88
+ })
89
+
90
+ const project = computed(() => projectData.value)
91
+
92
+ // Provide content ID for ProseImg/ProseVideo to resolve relative asset paths
93
+ provide('contentId', computed(() => project.value?._id || ''))
94
+
95
+ const projectImage = computed(() => {
96
+ const resolved = resolveContentImage(project.value?.image, project.value?._id)
97
+ if (!resolved) return ''
98
+ const basePath = config.app.baseURL || ''
99
+ if (!basePath || basePath === '/') return resolved
100
+ return basePath + resolved
101
+ })
102
+
103
+ useHead({
104
+ title: computed(() => project.value ? `${project.value.title} - ${t('site.shortName')}` : 'Project Not Found'),
105
+ meta: computed(() => {
106
+ const description = project.value?.description || project.value?.body
107
+ ? (typeof (project.value.description || project.value.body) === 'string'
108
+ ? (project.value.description || project.value.body).substring(0, 160).replace(/<[^>]*>/g, '')
109
+ : `${t('site.shortName')} project`)
110
+ : `${t('site.shortName')} project page`
111
+ return [
112
+ { name: 'description', content: description }
113
+ ]
114
+ })
115
+ })
116
+ </script>
117
+
118
+ <style scoped>
119
+ .project-page {
120
+ min-height: 100vh;
121
+ position: relative;
122
+ }
123
+
124
+ /* Page Container with top padding for fixed header */
125
+ .project-page__container {
126
+ padding-top: 100px;
127
+ }
128
+
129
+ /* Background */
130
+ .project__bg {
131
+ position: fixed;
132
+ inset: 0;
133
+ background: linear-gradient(180deg, var(--color-bg-alt) 0%, var(--color-bg) 100%);
134
+ z-index: -2;
135
+ pointer-events: none;
136
+ }
137
+
138
+ .project__pattern {
139
+ position: absolute;
140
+ inset: 0;
141
+ background-image:
142
+ linear-gradient(rgba(10, 37, 64, 0.03) 1px, transparent 1px),
143
+ linear-gradient(90deg, rgba(10, 37, 64, 0.03) 1px, transparent 1px);
144
+ background-size: 40px 40px;
145
+ }
146
+
147
+ .project__shapes {
148
+ position: absolute;
149
+ inset: 0;
150
+ overflow: hidden;
151
+ }
152
+
153
+ .project__shape {
154
+ position: absolute;
155
+ border-radius: 50%;
156
+ animation: float 8s ease-in-out infinite;
157
+ }
158
+
159
+ .project__shape--1 {
160
+ width: 200px;
161
+ height: 200px;
162
+ top: 10%;
163
+ right: 8%;
164
+ background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-secondary) 100%);
165
+ opacity: 0.12;
166
+ animation-delay: 0s;
167
+ }
168
+
169
+ .project__shape--2 {
170
+ width: 120px;
171
+ height: 120px;
172
+ bottom: 20%;
173
+ left: 5%;
174
+ background: var(--color-primary);
175
+ opacity: 0.08;
176
+ animation-delay: 2.5s;
177
+ }
178
+
179
+ @keyframes float {
180
+ 0%, 100% {
181
+ transform: translateY(0) rotate(0deg);
182
+ }
183
+ 50% {
184
+ transform: translateY(-20px) rotate(3deg);
185
+ }
186
+ }
187
+
188
+ /* Back Button */
189
+ .project__back {
190
+ display: inline-flex;
191
+ align-items: center;
192
+ gap: var(--spacing-sm);
193
+ padding: var(--spacing-sm) var(--spacing-md);
194
+ background: rgba(255, 255, 255, 0.9);
195
+ border: 1px solid var(--color-border);
196
+ border-radius: var(--radius-full);
197
+ color: var(--color-text);
198
+ font-size: 0.875rem;
199
+ font-weight: 500;
200
+ text-decoration: none;
201
+ transition: all var(--transition-base);
202
+ margin-bottom: var(--spacing-xl);
203
+ }
204
+
205
+ .project__back:hover {
206
+ background: var(--color-primary);
207
+ border-color: var(--color-primary);
208
+ color: white;
209
+ transform: translateX(-3px);
210
+ }
211
+
212
+ /* Project Header */
213
+ .project-header {
214
+ display: grid;
215
+ grid-template-columns: 1fr 1fr;
216
+ gap: var(--spacing-2xl);
217
+ align-items: start;
218
+ margin-bottom: var(--spacing-3xl);
219
+ }
220
+
221
+ .project-header__image-wrapper {
222
+ position: relative;
223
+ border-radius: var(--radius-xl);
224
+ overflow: hidden;
225
+ box-shadow: var(--shadow-lg);
226
+ }
227
+
228
+ .project-header__image {
229
+ width: 100%;
230
+ height: auto;
231
+ display: block;
232
+ }
233
+
234
+ .project-header__info {
235
+ display: flex;
236
+ flex-direction: column;
237
+ gap: var(--spacing-sm);
238
+ }
239
+
240
+ .project-header__badge {
241
+ display: inline-flex;
242
+ align-items: center;
243
+ gap: var(--spacing-xs);
244
+ padding: var(--spacing-xs) var(--spacing-sm);
245
+ font-size: 0.7rem;
246
+ font-weight: 700;
247
+ letter-spacing: 0.08em;
248
+ text-transform: uppercase;
249
+ border-radius: var(--radius-full);
250
+ align-self: flex-start;
251
+ }
252
+
253
+ .badge-ongoing {
254
+ background: #dbeafe;
255
+ color: #1e40af;
256
+ }
257
+
258
+ .badge-completed {
259
+ background: #d1fae5;
260
+ color: #065f46;
261
+ }
262
+
263
+ .badge-open {
264
+ background: #fef3c7;
265
+ color: #92400e;
266
+ }
267
+
268
+ .badge-maintained {
269
+ background: #ede9fe;
270
+ color: #5b21b6;
271
+ }
272
+
273
+ .project-header__name {
274
+ font-family: var(--font-display);
275
+ font-size: clamp(1.75rem, 4vw, 2.25rem);
276
+ font-weight: 800;
277
+ line-height: 1.15;
278
+ color: var(--color-primary);
279
+ margin: 0;
280
+ }
281
+
282
+ .project-header__year {
283
+ font-size: 0.9375rem;
284
+ font-weight: 500;
285
+ color: var(--color-text-muted);
286
+ }
287
+
288
+ /* Content Area */
289
+ .project-content {
290
+ display: flex;
291
+ flex-direction: column;
292
+ gap: var(--spacing-xl);
293
+ margin-bottom: var(--spacing-3xl);
294
+ }
295
+
296
+ /* Project Sections */
297
+ .project-section {
298
+ background: var(--color-bg-alt);
299
+ border-radius: var(--radius-xl);
300
+ border: 1px solid var(--color-border);
301
+ box-shadow: var(--shadow-md);
302
+ overflow: hidden;
303
+ transition: all var(--transition-base);
304
+ }
305
+
306
+ .project-section:hover {
307
+ box-shadow: var(--shadow-lg);
308
+ border-color: var(--color-secondary);
309
+ }
310
+
311
+ .project-section__header {
312
+ display: flex;
313
+ align-items: center;
314
+ gap: var(--spacing-sm);
315
+ padding: var(--spacing-lg) var(--spacing-xl);
316
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light) 100%);
317
+ border-bottom: 1px solid var(--color-border);
318
+ }
319
+
320
+ .project-section__header h3 {
321
+ font-family: var(--font-display);
322
+ font-size: 1.125rem;
323
+ font-weight: 600;
324
+ color: white;
325
+ margin: 0;
326
+ }
327
+
328
+ .project-section__body {
329
+ padding: var(--spacing-xl);
330
+ }
331
+
332
+ .project-section__body--content {
333
+ padding: var(--spacing-xl);
334
+ background: white;
335
+ }
336
+
337
+ /* ContentRenderer Markdown Styling */
338
+ .project-section__body--content :deep(h2) {
339
+ font-family: var(--font-display);
340
+ font-size: 1.5rem;
341
+ font-weight: 700;
342
+ color: var(--color-primary);
343
+ margin-top: var(--spacing-lg);
344
+ margin-bottom: var(--spacing-md);
345
+ }
346
+
347
+ .project-section__body--content :deep(h3) {
348
+ font-family: var(--font-display);
349
+ font-size: 1.25rem;
350
+ font-weight: 600;
351
+ color: var(--color-primary);
352
+ margin-top: var(--spacing-md);
353
+ margin-bottom: var(--spacing-sm);
354
+ }
355
+
356
+ .project-section__body--content :deep(p) {
357
+ font-size: 1rem;
358
+ line-height: 1.8;
359
+ color: var(--color-text);
360
+ margin-bottom: var(--spacing-md);
361
+ }
362
+
363
+ .project-section__body--content :deep(ul) {
364
+ list-style: none;
365
+ padding-left: 0;
366
+ }
367
+
368
+ .project-section__body--content :deep(li) {
369
+ padding-left: var(--spacing-md);
370
+ margin-bottom: var(--spacing-sm);
371
+ position: relative;
372
+ }
373
+
374
+ /* Task-list (checkbox) items use the checkbox as their marker, not the
375
+ bullet, so they need much less left padding than normal list items. */
376
+ .project-section__body--content :deep(.task-list-item) {
377
+ padding-left: 0;
378
+ }
379
+
380
+ .project-section__body--content :deep(li::before) {
381
+ content: '•';
382
+ position: absolute;
383
+ left: 0;
384
+ color: var(--color-accent);
385
+ font-weight: 700;
386
+ }
387
+
388
+ .project-section__body--content :deep(a) {
389
+ color: var(--color-secondary);
390
+ text-decoration: none;
391
+ transition: color var(--transition-fast);
392
+ }
393
+
394
+ .project-section__body--content :deep(a:hover) {
395
+ color: var(--color-accent);
396
+ }
397
+
398
+ .project-section__body--content :deep(strong) {
399
+ font-weight: 600;
400
+ color: var(--color-primary);
401
+ }
402
+
403
+ /* Not Found */
404
+ .not-found-page {
405
+ min-height: 60vh;
406
+ display: flex;
407
+ align-items: center;
408
+ }
409
+
410
+ .not-found {
411
+ text-align: center;
412
+ padding: var(--spacing-4xl) 0;
413
+ max-width: 400px;
414
+ margin: 0 auto;
415
+ }
416
+
417
+ .not-found h1 {
418
+ font-family: var(--font-display);
419
+ font-size: clamp(2rem, 4vw, 2.5rem);
420
+ font-weight: 700;
421
+ color: var(--color-primary);
422
+ margin-bottom: var(--spacing-md);
423
+ }
424
+
425
+ .not-found p {
426
+ font-size: 1rem;
427
+ color: var(--color-text-muted);
428
+ margin-bottom: var(--spacing-xl);
429
+ }
430
+
431
+ /* Responsive */
432
+ @media (max-width: 768px) {
433
+ .project-header {
434
+ grid-template-columns: 1fr;
435
+ }
436
+
437
+ .project-content {
438
+ gap: var(--spacing-lg);
439
+ }
440
+ }
441
+ </style>