@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,225 @@
1
+ <template>
2
+ <footer class="footer">
3
+ <div class="container footer__inner">
4
+ <!-- Brand -->
5
+ <div class="footer__brand">
6
+ <div class="footer__logo">
7
+ <span class="footer__logo-text">{{ t('footer.brand') }}</span>
8
+ <span class="footer__logo-sub">{{ t('footer.university') }}</span>
9
+ </div>
10
+ <p class="footer__tagline">
11
+ {{ t('footer.tagline') }}
12
+ </p>
13
+ </div>
14
+
15
+ <!-- Quick Links -->
16
+ <div class="footer__links">
17
+ <h4 class="footer__heading">{{ t('footer.quickLinks') }}</h4>
18
+ <nav class="footer__nav">
19
+ <NuxtLink
20
+ v-for="item in quickLinks"
21
+ :key="item.to"
22
+ :to="item.to"
23
+ class="footer__link"
24
+ >{{ t(item.labelKey) }}</NuxtLink>
25
+ </nav>
26
+ </div>
27
+
28
+ <!-- Contact -->
29
+ <div class="footer__contact">
30
+ <h4 class="footer__heading">{{ t('footer.contact') }}</h4>
31
+ <address class="footer__address">
32
+ <p>
33
+ <strong>{{ t('footer.director') }}</strong><br>
34
+ {{ t('footer.directorTitle') }}<br>
35
+ {{ t('footer.department') }}
36
+ </p>
37
+ <p v-if="contactEmail">
38
+ <a :href="`mailto:${contactEmail}`" class="footer__email">
39
+ {{ contactEmail }}
40
+ </a>
41
+ </p>
42
+ </address>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Bottom Bar -->
47
+ <div class="footer__bottom">
48
+ <div class="container footer__bottom-inner">
49
+ <p class="footer__copyright">
50
+ &copy; {{ currentYear }} {{ t('footer.copyright') }}
51
+ </p>
52
+ <div class="footer__external" v-if="externalUrl">
53
+ <a :href="externalUrl" target="_blank" rel="noopener" class="footer__external-link">
54
+ {{ t(appConfig.markuxt?.contact?.externalLabelKey || 'footer.universityLink') }}
55
+ <LinkOut class="icon-inline" theme="outline" :size="12" fill="currentColor" :stroke-width="2" />
56
+ </a>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </footer>
61
+ </template>
62
+
63
+ <script setup lang="ts">
64
+ import LinkOut from '@icon-park/vue-next/es/icons/LinkOut'
65
+
66
+ const { t } = useI18n()
67
+ const appConfig = useAppConfig()
68
+ const currentYear = new Date().getFullYear()
69
+
70
+ const contactEmail = computed(() => appConfig.markuxt?.contact?.email || '')
71
+ const externalUrl = computed(() => appConfig.markuxt?.contact?.externalUrl || '')
72
+
73
+ // Quick links from navigation config (exclude home)
74
+ const quickLinks = computed(() =>
75
+ (appConfig.markuxt?.navigation || []).filter(item => item.to !== '/')
76
+ )
77
+ </script>
78
+
79
+ <style scoped>
80
+ .footer {
81
+ background: var(--color-primary);
82
+ color: white;
83
+ padding-top: var(--spacing-3xl);
84
+ padding-bottom: var(--spacing-lg);
85
+ }
86
+
87
+ .footer__inner {
88
+ display: grid;
89
+ grid-template-columns: 2fr 1fr 1fr;
90
+ gap: var(--spacing-2xl);
91
+ }
92
+
93
+ /* Brand */
94
+ .footer__brand {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: var(--spacing-md);
98
+ }
99
+
100
+ .footer__logo {
101
+ display: flex;
102
+ flex-direction: column;
103
+ }
104
+
105
+ .footer__logo-text {
106
+ font-family: var(--font-display);
107
+ font-size: 1.5rem;
108
+ font-weight: 700;
109
+ line-height: 1.2;
110
+ }
111
+
112
+ .footer__logo-sub {
113
+ font-size: 0.875rem;
114
+ opacity: 0.8;
115
+ font-weight: 400;
116
+ }
117
+
118
+ .footer__tagline {
119
+ font-size: 0.9375rem;
120
+ line-height: 1.6;
121
+ opacity: 0.7;
122
+ max-width: 300px;
123
+ margin: 0;
124
+ }
125
+
126
+ /* Links */
127
+ .footer__heading {
128
+ font-family: var(--font-body);
129
+ font-size: 0.875rem;
130
+ font-weight: 600;
131
+ text-transform: uppercase;
132
+ letter-spacing: 0.05em;
133
+ color: var(--color-accent);
134
+ margin-bottom: var(--spacing-md);
135
+ }
136
+
137
+ .footer__nav {
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: var(--spacing-sm);
141
+ }
142
+
143
+ .footer__link {
144
+ font-size: 0.9375rem;
145
+ color: rgba(255, 255, 255, 0.8);
146
+ text-decoration: none;
147
+ transition: color 0.2s ease;
148
+ }
149
+
150
+ .footer__link:hover {
151
+ color: var(--color-accent);
152
+ }
153
+
154
+ /* Contact */
155
+ .footer__address {
156
+ font-style: normal;
157
+ font-size: 0.9375rem;
158
+ line-height: 1.6;
159
+ opacity: 0.8;
160
+ }
161
+
162
+ .footer__address p {
163
+ margin-bottom: var(--spacing-sm);
164
+ }
165
+
166
+ .footer__email {
167
+ color: var(--color-accent);
168
+ text-decoration: none;
169
+ }
170
+
171
+ .footer__email:hover {
172
+ text-decoration: underline;
173
+ }
174
+
175
+ /* Bottom Bar */
176
+ .footer__bottom {
177
+ margin-top: var(--spacing-2xl);
178
+ padding-top: var(--spacing-lg);
179
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
180
+ }
181
+
182
+ .footer__bottom-inner {
183
+ display: flex;
184
+ justify-content: space-between;
185
+ align-items: center;
186
+ }
187
+
188
+ .footer__copyright {
189
+ font-size: 0.875rem;
190
+ opacity: 0.6;
191
+ margin: 0;
192
+ }
193
+
194
+ .footer__external-link {
195
+ display: inline-flex;
196
+ align-items: center;
197
+ gap: var(--spacing-xs);
198
+ font-size: 0.875rem;
199
+ color: rgba(255, 255, 255, 0.7);
200
+ text-decoration: none;
201
+ transition: color 0.2s ease;
202
+ }
203
+
204
+ .footer__external-link:hover {
205
+ color: var(--color-accent);
206
+ }
207
+
208
+ /* Responsive */
209
+ @media (max-width: 768px) {
210
+ .footer {
211
+ padding-top: var(--spacing-2xl);
212
+ }
213
+
214
+ .footer__inner {
215
+ grid-template-columns: 1fr;
216
+ gap: var(--spacing-xl);
217
+ }
218
+
219
+ .footer__bottom-inner {
220
+ flex-direction: column;
221
+ gap: var(--spacing-md);
222
+ text-align: center;
223
+ }
224
+ }
225
+ </style>
@@ -0,0 +1,342 @@
1
+ <template>
2
+ <header class="header" :class="{ 'header--scrolled': scrolled }">
3
+ <div class="container header__inner">
4
+ <!-- Logo -->
5
+ <NuxtLink to="/" class="header__logo">
6
+ <img :src="logoSrc" :alt="t('nav.universityAlt')" class="header__logo-img">
7
+ <div class="header__logo-text">
8
+ <span class="header__logo-main">{{ t('nav.brand') }}</span>
9
+ <span class="header__logo-sub">{{ t('nav.university') }}</span>
10
+ </div>
11
+ </NuxtLink>
12
+
13
+ <!-- Desktop Navigation -->
14
+ <nav class="header__nav">
15
+ <NuxtLink
16
+ v-for="item in navigation"
17
+ :key="item.to"
18
+ :to="item.to"
19
+ class="header__link"
20
+ :class="{ 'header__link--active': isActive(item.to) }"
21
+ >
22
+ {{ item.label }}
23
+ </NuxtLink>
24
+ <LanguageSwitcher />
25
+ </nav>
26
+
27
+ <!-- Mobile Menu Button -->
28
+ <button
29
+ class="header__toggle"
30
+ @click="toggleMobileMenu"
31
+ :aria-label="t('nav.toggleMenu')"
32
+ :aria-expanded="mobileMenuOpen"
33
+ >
34
+ <span class="header__hamburger" :class="{ 'header__hamburger--open': mobileMenuOpen }">
35
+ <span></span>
36
+ <span></span>
37
+ <span></span>
38
+ </span>
39
+ </button>
40
+ </div>
41
+
42
+ <!-- Mobile Navigation -->
43
+ <Transition name="mobile-menu">
44
+ <nav class="header__mobile-nav" v-if="mobileMenuOpen">
45
+ <NuxtLink
46
+ v-for="item in navigation"
47
+ :key="item.to"
48
+ :to="item.to"
49
+ class="header__mobile-link"
50
+ @click="mobileMenuOpen = false"
51
+ >
52
+ {{ item.label }}
53
+ </NuxtLink>
54
+ <LanguageSwitcher />
55
+ </nav>
56
+ </Transition>
57
+ </header>
58
+ </template>
59
+
60
+ <script setup lang="ts">
61
+ import { ref, onMounted, onUnmounted, computed } from 'vue'
62
+
63
+ const route = useRoute()
64
+ const config = useRuntimeConfig()
65
+
66
+ const { t } = useI18n()
67
+ const appConfig = useAppConfig()
68
+
69
+ const logoSrc = computed(() => {
70
+ const basePath = config.app.baseURL || '/'
71
+ const base = basePath.endsWith('/') ? basePath : basePath + '/'
72
+ const src = appConfig.markuxt?.logo?.src || '/images/logo.png'
73
+ return base + src.replace(/^\//, '')
74
+ })
75
+
76
+ const navigation = computed(() =>
77
+ (appConfig.markuxt?.navigation || []).map(item => ({
78
+ to: item.to,
79
+ label: t(item.labelKey)
80
+ }))
81
+ )
82
+
83
+ const scrolled = ref(false)
84
+ const mobileMenuOpen = ref(false)
85
+
86
+ const isActive = (path: string) => {
87
+ if (path === '/') {
88
+ return route.path === '/'
89
+ }
90
+ return route.path.startsWith(path)
91
+ }
92
+
93
+ const handleScroll = () => {
94
+ scrolled.value = window.scrollY > 20
95
+ }
96
+
97
+ const toggleMobileMenu = () => {
98
+ mobileMenuOpen.value = !mobileMenuOpen.value
99
+ }
100
+
101
+ onMounted(() => {
102
+ window.addEventListener('scroll', handleScroll)
103
+ })
104
+
105
+ onUnmounted(() => {
106
+ window.removeEventListener('scroll', handleScroll)
107
+ })
108
+ </script>
109
+
110
+ <style scoped>
111
+ .header {
112
+ position: fixed;
113
+ top: 0;
114
+ left: 0;
115
+ right: 0;
116
+ z-index: 100;
117
+ background: rgba(255, 255, 255, 0.95);
118
+ backdrop-filter: blur(10px);
119
+ border-bottom: 1px solid transparent;
120
+ transition: all 0.3s ease;
121
+ }
122
+
123
+ .header--scrolled {
124
+ background: rgba(255, 255, 255, 0.98);
125
+ box-shadow: 0 2px 20px rgba(0, 0, 0, 0.08);
126
+ border-bottom-color: var(--color-border);
127
+ }
128
+
129
+ .header__inner {
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: space-between;
133
+ height: 80px;
134
+ max-width: 1400px;
135
+ margin-left: auto;
136
+ margin-right: auto;
137
+ padding-left: var(--spacing-lg);
138
+ padding-right: var(--spacing-lg);
139
+ }
140
+
141
+ /* Logo */
142
+ .header__logo {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: var(--spacing-md);
146
+ text-decoration: none;
147
+ transition: opacity 0.2s ease;
148
+ }
149
+
150
+ .header__logo:hover {
151
+ opacity: 0.8;
152
+ }
153
+
154
+ .header__logo-img {
155
+ width: 40px;
156
+ height: 40px;
157
+ flex-shrink: 0;
158
+ object-fit: contain;
159
+ }
160
+
161
+ .header__logo-text {
162
+ display: flex;
163
+ flex-direction: column;
164
+ }
165
+
166
+ .header__logo-main {
167
+ font-family: var(--font-display);
168
+ font-size: 1.125rem;
169
+ font-weight: 700;
170
+ line-height: 1.1;
171
+ color: var(--color-primary);
172
+ }
173
+
174
+ .header__logo-sub {
175
+ font-size: 0.625rem;
176
+ font-weight: 500;
177
+ color: var(--color-secondary);
178
+ letter-spacing: 0.02em;
179
+ }
180
+
181
+ /* Desktop Navigation */
182
+ .header__nav {
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 0.25rem;
186
+ }
187
+
188
+ .header__link {
189
+ padding: 0.5rem 0.875rem;
190
+ font-size: 0.875rem;
191
+ font-weight: 500;
192
+ color: var(--color-text);
193
+ text-decoration: none;
194
+ border-radius: var(--radius-md);
195
+ transition: all 0.2s ease;
196
+ position: relative;
197
+ }
198
+
199
+ .header__link:hover {
200
+ color: var(--color-secondary);
201
+ background: rgba(0, 155, 193, 0.08);
202
+ }
203
+
204
+ .header__link--active {
205
+ color: var(--color-secondary);
206
+ font-weight: 600;
207
+ }
208
+
209
+ .header__link--active::after {
210
+ content: '';
211
+ position: absolute;
212
+ bottom: 0;
213
+ left: 50%;
214
+ transform: translateX(-50%);
215
+ width: 24px;
216
+ height: 2px;
217
+ background: var(--color-secondary);
218
+ border-radius: 1px;
219
+ }
220
+
221
+ /* Mobile Toggle */
222
+ .header__toggle {
223
+ display: none;
224
+ flex-direction: column;
225
+ justify-content: center;
226
+ align-items: center;
227
+ width: 44px;
228
+ height: 44px;
229
+ padding: 0;
230
+ background: none;
231
+ border: none;
232
+ cursor: pointer;
233
+ }
234
+
235
+ .header__hamburger {
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 5px;
239
+ }
240
+
241
+ .header__hamburger span {
242
+ display: block;
243
+ width: 24px;
244
+ height: 2px;
245
+ background: var(--color-primary);
246
+ border-radius: 1px;
247
+ transition: all 0.3s ease;
248
+ }
249
+
250
+ .header__hamburger--open span:nth-child(1) {
251
+ transform: translateY(7px) rotate(45deg);
252
+ }
253
+
254
+ .header__hamburger--open span:nth-child(2) {
255
+ opacity: 0;
256
+ }
257
+
258
+ .header__hamburger--open span:nth-child(3) {
259
+ transform: translateY(-7px) rotate(-45deg);
260
+ }
261
+
262
+ /* Mobile Navigation */
263
+ .header__mobile-nav {
264
+ display: none;
265
+ flex-direction: column;
266
+ align-items: center;
267
+ padding: var(--spacing-lg);
268
+ background: white;
269
+ border-top: 1px solid var(--color-border);
270
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
271
+ }
272
+
273
+ .header__mobile-nav .lang-switcher {
274
+ margin-top: var(--spacing-md);
275
+ }
276
+
277
+ .header__mobile-link {
278
+ padding: var(--spacing-md);
279
+ font-size: 1rem;
280
+ font-weight: 500;
281
+ color: var(--color-text);
282
+ text-decoration: none;
283
+ border-radius: var(--radius-md);
284
+ transition: all 0.2s ease;
285
+ }
286
+
287
+ .header__mobile-link:hover,
288
+ .header__mobile-link.router-link-active {
289
+ background: var(--color-bg);
290
+ color: var(--color-secondary);
291
+ }
292
+
293
+ /* Mobile Menu Transition */
294
+ .mobile-menu-enter-active,
295
+ .mobile-menu-leave-active {
296
+ transition: all 0.3s ease;
297
+ }
298
+
299
+ .mobile-menu-enter-from,
300
+ .mobile-menu-leave-to {
301
+ opacity: 0;
302
+ transform: translateY(-10px);
303
+ }
304
+
305
+ /* Responsive */
306
+ @media (max-width: 1024px) {
307
+ .header__logo-main {
308
+ font-size: 1rem;
309
+ }
310
+
311
+ .header__logo-sub {
312
+ font-size: 0.5625rem;
313
+ }
314
+
315
+ .header__nav {
316
+ display: none;
317
+ }
318
+
319
+ .header__toggle {
320
+ display: flex;
321
+ }
322
+
323
+ .header__mobile-nav {
324
+ display: flex;
325
+ }
326
+ }
327
+
328
+ @media (max-width: 640px) {
329
+ .header__inner {
330
+ padding-left: var(--spacing-md);
331
+ padding-right: var(--spacing-md);
332
+ }
333
+
334
+ .header__logo-main {
335
+ font-size: 0.875rem;
336
+ }
337
+
338
+ .header__logo-sub {
339
+ display: none;
340
+ }
341
+ }
342
+ </style>