@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,425 @@
1
+ <template>
2
+ <main class="position-page" v-if="position">
3
+ <!-- Decorative background -->
4
+ <div class="position__bg">
5
+ <div class="position__pattern"></div>
6
+ <div class="position__shapes">
7
+ <span class="position__shape position__shape--1"></span>
8
+ <span class="position__shape position__shape--2"></span>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="container position-page__container">
13
+ <!-- Back button -->
14
+ <NuxtLink to="/positions" class="position__back">
15
+ <ArrowLeft class="icon-inline" theme="outline" :size="16" fill="currentColor" :stroke-width="2" />
16
+ {{ t('positions.backTo') }}
17
+ </NuxtLink>
18
+
19
+ <!-- Position Header -->
20
+ <div class="position-header animate-fade-in-up">
21
+ <div class="position-header__info">
22
+ <span v-if="position.type" class="position-header__badge badge-accent">
23
+ {{ position.type }}
24
+ </span>
25
+ <h1 class="position-header__name">{{ position.title }}</h1>
26
+ </div>
27
+ </div>
28
+
29
+ <!-- Position Content -->
30
+ <div class="position-content">
31
+ <!-- Description -->
32
+ <div v-if="position.body || position.description" class="position-section animate-fade-in-up delay-200">
33
+ <div class="position-section__header">
34
+ <FileStaff class="icon-inline" theme="outline" :size="20" fill="white" :stroke-width="2.8" />
35
+ <h3>{{ t('positions.aboutPosition') }}</h3>
36
+ </div>
37
+ <div class="position-section__body position-section__body--content">
38
+ <ContentRenderer :value="position" />
39
+ </div>
40
+ </div>
41
+
42
+ <!-- Requirements -->
43
+ <div v-if="position.requirements" class="position-section animate-fade-in-up delay-300">
44
+ <div class="position-section__header">
45
+ <CheckCorrect class="icon-inline" theme="outline" :size="20" fill="white" :stroke-width="2.8" />
46
+ <h3>{{ t('positions.requirements') }}</h3>
47
+ </div>
48
+ <div class="position-section__body">
49
+ <ul class="requirements-list">
50
+ <li v-for="(req, index) in position.requirements" :key="index">{{ req }}</li>
51
+ </ul>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Apply Button -->
56
+ <div v-if="position.email" class="animate-fade-in-up delay-400">
57
+ <a
58
+ :href="`mailto:${position.email}`"
59
+ class="apply-link"
60
+ >
61
+ <Mail class="icon-inline" theme="outline" :size="20" fill="currentColor" :stroke-width="3" />
62
+ {{ t('positions.apply') }}
63
+ </a>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </main>
68
+
69
+ <!-- Not Found -->
70
+ <div v-else class="not-found-page">
71
+ <div class="container">
72
+ <div class="not-found">
73
+ <Help class="icon-inline" theme="outline" :size="80" fill="var(--color-accent)" :stroke-width="3" />
74
+ <h1>{{ t('positions.notFound') }}</h1>
75
+ <p>{{ t('positions.notFoundDesc') }}</p>
76
+ <NuxtLink to="/positions" class="btn btn-primary">{{ t('positions.browseAll') }}</NuxtLink>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </template>
81
+
82
+ <script setup lang="ts">
83
+ import ArrowLeft from '@icon-park/vue-next/es/icons/ArrowLeft'
84
+ import FileStaff from '@icon-park/vue-next/es/icons/FileStaff'
85
+ import CheckCorrect from '@icon-park/vue-next/es/icons/CheckCorrect'
86
+ import Mail from '@icon-park/vue-next/es/icons/Mail'
87
+ import Help from '@icon-park/vue-next/es/icons/Help'
88
+
89
+ const { t } = useI18n()
90
+ const route = useRoute()
91
+
92
+ // Get position by file path
93
+ // For catch-all route [...slug], params.slug is an array
94
+ const slug = computed(() => {
95
+ const slugParam = route.params.slug
96
+ return Array.isArray(slugParam) ? slugParam.join('/') : slugParam
97
+ })
98
+
99
+ const { data: positionData } = await useAsyncData(`position-${slug.value}`, async () => {
100
+ try {
101
+ const fullPath = `/positions/${slug.value}`
102
+ return await queryContent(fullPath).findOne()
103
+ } catch (e) {
104
+ console.error('Error fetching position:', e)
105
+ return null
106
+ }
107
+ }, {
108
+ watch: [slug]
109
+ })
110
+
111
+ const position = computed(() => positionData.value)
112
+
113
+ // Provide content ID for ProseImg/ProseVideo to resolve relative asset paths
114
+ provide('contentId', computed(() => position.value?._id || ''))
115
+
116
+ useHead({
117
+ title: computed(() => position.value ? `${position.value.title} - ${t('site.shortName')}` : 'Position Not Found'),
118
+ meta: computed(() => {
119
+ const description = position.value?.description || position.value?.body
120
+ ? (typeof (position.value.description || position.value.body) === 'string'
121
+ ? (position.value.description || position.value.body).substring(0, 160).replace(/<[^>]*>/g, '')
122
+ : `${t('site.shortName')} position`)
123
+ : `${t('site.shortName')} position page`
124
+ return [
125
+ { name: 'description', content: description }
126
+ ]
127
+ })
128
+ })
129
+ </script>
130
+
131
+ <style scoped>
132
+ .position-page {
133
+ min-height: 100vh;
134
+ position: relative;
135
+ }
136
+
137
+ /* Page Container with top padding for fixed header */
138
+ .position-page__container {
139
+ padding-top: 100px;
140
+ }
141
+
142
+ /* Background */
143
+ .position__bg {
144
+ position: fixed;
145
+ inset: 0;
146
+ background: linear-gradient(180deg, var(--color-bg-alt) 0%, var(--color-bg) 100%);
147
+ z-index: -2;
148
+ pointer-events: none;
149
+ }
150
+
151
+ .position__pattern {
152
+ position: absolute;
153
+ inset: 0;
154
+ background-image:
155
+ linear-gradient(rgba(10, 37, 64, 0.03) 1px, transparent 1px),
156
+ linear-gradient(90deg, rgba(10, 37, 64, 0.03) 1px, transparent 1px);
157
+ background-size: 40px 40px;
158
+ }
159
+
160
+ .position__shapes {
161
+ position: absolute;
162
+ inset: 0;
163
+ overflow: hidden;
164
+ }
165
+
166
+ .position__shape {
167
+ position: absolute;
168
+ border-radius: 50%;
169
+ animation: float 8s ease-in-out infinite;
170
+ }
171
+
172
+ .position__shape--1 {
173
+ width: 200px;
174
+ height: 200px;
175
+ top: 10%;
176
+ right: 8%;
177
+ background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-secondary) 100%);
178
+ opacity: 0.12;
179
+ animation-delay: 0s;
180
+ }
181
+
182
+ .position__shape--2 {
183
+ width: 120px;
184
+ height: 120px;
185
+ bottom: 20%;
186
+ left: 5%;
187
+ background: var(--color-primary);
188
+ opacity: 0.08;
189
+ animation-delay: 2.5s;
190
+ }
191
+
192
+ @keyframes float {
193
+ 0%, 100% {
194
+ transform: translateY(0) rotate(0deg);
195
+ }
196
+ 50% {
197
+ transform: translateY(-20px) rotate(3deg);
198
+ }
199
+ }
200
+
201
+ /* Back Button */
202
+ .position__back {
203
+ display: inline-flex;
204
+ align-items: center;
205
+ gap: var(--spacing-sm);
206
+ padding: var(--spacing-sm) var(--spacing-md);
207
+ background: rgba(255, 255, 255, 0.9);
208
+ border: 1px solid var(--color-border);
209
+ border-radius: var(--radius-full);
210
+ color: var(--color-text);
211
+ font-size: 0.875rem;
212
+ font-weight: 500;
213
+ text-decoration: none;
214
+ transition: all var(--transition-base);
215
+ margin-bottom: var(--spacing-xl);
216
+ }
217
+
218
+ .position__back:hover {
219
+ background: var(--color-primary);
220
+ border-color: var(--color-primary);
221
+ color: white;
222
+ transform: translateX(-3px);
223
+ }
224
+
225
+ /* Position Header */
226
+ .position-header {
227
+ margin-bottom: var(--spacing-3xl);
228
+ }
229
+
230
+ .position-header__info {
231
+ display: flex;
232
+ flex-direction: column;
233
+ gap: var(--spacing-sm);
234
+ }
235
+
236
+ .position-header__badge {
237
+ display: inline-flex;
238
+ align-items: center;
239
+ padding: var(--spacing-xs) var(--spacing-sm);
240
+ font-size: 0.7rem;
241
+ font-weight: 700;
242
+ letter-spacing: 0.08em;
243
+ text-transform: uppercase;
244
+ color: white;
245
+ background: var(--color-accent);
246
+ border-radius: var(--radius-sm);
247
+ align-self: flex-start;
248
+ }
249
+
250
+ .position-header__name {
251
+ font-family: var(--font-display);
252
+ font-size: clamp(1.75rem, 4vw, 2.25rem);
253
+ font-weight: 800;
254
+ line-height: 1.15;
255
+ color: var(--color-primary);
256
+ margin: 0;
257
+ }
258
+
259
+ /* Content Area */
260
+ .position-content {
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: var(--spacing-xl);
264
+ margin-bottom: var(--spacing-3xl);
265
+ }
266
+
267
+ /* Position Sections */
268
+ .position-section {
269
+ background: var(--color-bg-alt);
270
+ border-radius: var(--radius-xl);
271
+ border: 1px solid var(--color-border);
272
+ box-shadow: var(--shadow-md);
273
+ overflow: hidden;
274
+ transition: all var(--transition-base);
275
+ }
276
+
277
+ .position-section:hover {
278
+ box-shadow: var(--shadow-lg);
279
+ border-color: var(--color-secondary);
280
+ }
281
+
282
+ .position-section__header {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: var(--spacing-sm);
286
+ padding: var(--spacing-lg) var(--spacing-xl);
287
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light) 100%);
288
+ border-bottom: 1px solid var(--color-border);
289
+ }
290
+
291
+ .position-section__header h3 {
292
+ font-family: var(--font-display);
293
+ font-size: 1.125rem;
294
+ font-weight: 600;
295
+ color: white;
296
+ margin: 0;
297
+ }
298
+
299
+ .position-section__body {
300
+ padding: var(--spacing-xl);
301
+ }
302
+
303
+ .position-section__body--content {
304
+ padding: var(--spacing-xl);
305
+ background: white;
306
+ }
307
+
308
+ /* Requirements List */
309
+ .requirements-list {
310
+ list-style: none;
311
+ padding-left: 0;
312
+ margin: 0;
313
+ }
314
+
315
+ .requirements-list li {
316
+ padding-left: var(--spacing-lg);
317
+ margin-bottom: var(--spacing-sm);
318
+ position: relative;
319
+ }
320
+
321
+ .requirements-list li::before {
322
+ content: '✓';
323
+ position: absolute;
324
+ left: 0;
325
+ color: var(--color-accent);
326
+ font-weight: 700;
327
+ font-size: 1.2em;
328
+ }
329
+
330
+ /* Apply Link */
331
+ .apply-link {
332
+ display: inline-flex;
333
+ align-items: center;
334
+ gap: var(--spacing-sm);
335
+ padding: var(--spacing-md) var(--spacing-xl);
336
+ background: var(--color-secondary);
337
+ color: white;
338
+ text-decoration: none;
339
+ border-radius: var(--radius-lg);
340
+ font-weight: 600;
341
+ transition: all var(--transition-base);
342
+ }
343
+
344
+ .apply-link:hover {
345
+ background: var(--color-accent);
346
+ transform: translateY(-2px);
347
+ box-shadow: var(--shadow-md);
348
+ }
349
+
350
+ /* ContentRenderer Markdown Styling */
351
+ .position-section__body--content :deep(h2) {
352
+ font-family: var(--font-display);
353
+ font-size: 1.5rem;
354
+ font-weight: 700;
355
+ color: var(--color-primary);
356
+ margin-top: var(--spacing-lg);
357
+ margin-bottom: var(--spacing-md);
358
+ }
359
+
360
+ .position-section__body--content :deep(p) {
361
+ font-size: 1rem;
362
+ line-height: 1.8;
363
+ color: var(--color-text);
364
+ margin-bottom: var(--spacing-md);
365
+ }
366
+
367
+ .position-section__body--content :deep(ul) {
368
+ list-style: none;
369
+ padding-left: 0;
370
+ }
371
+
372
+ .position-section__body--content :deep(li) {
373
+ padding-left: var(--spacing-lg);
374
+ margin-bottom: var(--spacing-sm);
375
+ position: relative;
376
+ }
377
+
378
+ .position-section__body--content :deep(li::before) {
379
+ content: '•';
380
+ position: absolute;
381
+ left: 0;
382
+ color: var(--color-accent);
383
+ font-weight: 700;
384
+ }
385
+
386
+ .position-section__body--content :deep(strong) {
387
+ font-weight: 600;
388
+ color: var(--color-primary);
389
+ }
390
+
391
+ /* Not Found */
392
+ .not-found-page {
393
+ min-height: 60vh;
394
+ display: flex;
395
+ align-items: center;
396
+ }
397
+
398
+ .not-found {
399
+ text-align: center;
400
+ padding: var(--spacing-4xl) 0;
401
+ max-width: 400px;
402
+ margin: 0 auto;
403
+ }
404
+
405
+ .not-found h1 {
406
+ font-family: var(--font-display);
407
+ font-size: clamp(2rem, 4vw, 2.5rem);
408
+ font-weight: 700;
409
+ color: var(--color-primary);
410
+ margin-bottom: var(--spacing-md);
411
+ }
412
+
413
+ .not-found p {
414
+ font-size: 1rem;
415
+ color: var(--color-text-muted);
416
+ margin-bottom: var(--spacing-xl);
417
+ }
418
+
419
+ /* Responsive */
420
+ @media (max-width: 768px) {
421
+ .position-content {
422
+ gap: var(--spacing-lg);
423
+ }
424
+ }
425
+ </style>
@@ -0,0 +1,266 @@
1
+ <template>
2
+ <div class="positions-page">
3
+ <div class="section">
4
+ <div class="container">
5
+ <SectionTitle
6
+ :overline="t('positions.overline')"
7
+ :title="t('positions.title')"
8
+ :description="t('positions.description')"
9
+ />
10
+
11
+ <div class="positions-intro">
12
+ <p>
13
+ {{ t('positions.intro') }}
14
+ </p>
15
+ </div>
16
+
17
+ <!-- Positions Grid -->
18
+ <div class="positions-grid" v-if="positionsList.length > 0">
19
+ <NuxtLink
20
+ v-for="position in positionsList"
21
+ :key="position._path"
22
+ :to="position._path || ''"
23
+ class="position-card"
24
+ >
25
+ <div class="position-card__header">
26
+ <h3 class="position-card__title">{{ position.title }}</h3>
27
+ <span v-if="position.type" class="badge badge-accent">{{ position.type }}</span>
28
+ </div>
29
+ <p class="position-card__description" v-if="position.description">
30
+ {{ position.description }}
31
+ </p>
32
+ <div class="position-card__footer">
33
+ <span class="position-card__cta">{{ t('positions.viewDetails') }}</span>
34
+ </div>
35
+ </NuxtLink>
36
+ </div>
37
+
38
+ <div class="positions-coming-soon" v-else>
39
+ <div class="placeholder-icon">
40
+ <Briefcase class="icon-inline" theme="outline" :size="64" fill="var(--color-accent)" :stroke-width="2" />
41
+ </div>
42
+ <h3>{{ t('positions.noPositions') }}</h3>
43
+ <p>{{ t('positions.checkBack') }}</p>
44
+ </div>
45
+
46
+ <!-- General Inquiry -->
47
+ <div class="positions-contact">
48
+ <h3>{{ t('positions.interested') }}</h3>
49
+ <p>
50
+ {{ t('positions.contactText') }}
51
+ </p>
52
+ <a :href="`mailto:${contactEmail}`" class="btn btn-primary" v-if="contactEmail">
53
+ {{ t('positions.contactDirector') }}
54
+ </a>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ import Briefcase from '@icon-park/vue-next/es/icons/Briefcase'
63
+
64
+ const { t } = useI18n()
65
+ const appConfig = useAppConfig()
66
+ const contactEmail = computed(() => appConfig.markuxt?.contact?.email || '')
67
+
68
+ interface Position {
69
+ title: string
70
+ description?: string
71
+ type?: string
72
+ _path?: string
73
+ }
74
+
75
+ // Fetch positions
76
+ const { data: positions } = await useAsyncData('positions', () =>
77
+ queryContent('/positions')
78
+ .where({ _hidden: { $ne: true } })
79
+ .where({ _extension: 'md' }).find()
80
+ )
81
+
82
+ const positionsList = computed(() => positions.value || [])
83
+
84
+ useHead({
85
+ title: t('positions.pageTitle'),
86
+ meta: [
87
+ { name: 'description', content: t('positions.pageDescription') }
88
+ ]
89
+ })
90
+ </script>
91
+
92
+ <style scoped>
93
+ .positions-page {
94
+ padding-top: var(--spacing-xl);
95
+ }
96
+
97
+ .positions-intro {
98
+ background: var(--color-bg-alt);
99
+ border-radius: var(--radius-xl);
100
+ padding: var(--spacing-xl);
101
+ margin-bottom: var(--spacing-3xl);
102
+ border-left: 4px solid var(--color-secondary);
103
+ }
104
+
105
+ .positions-intro p {
106
+ font-size: 1.0625rem;
107
+ line-height: 1.7;
108
+ color: var(--color-text-muted);
109
+ margin: 0;
110
+ }
111
+
112
+ .positions-grid {
113
+ display: grid;
114
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
115
+ gap: var(--spacing-xl);
116
+ margin-bottom: var(--spacing-3xl);
117
+ }
118
+
119
+ .position-card {
120
+ background: var(--color-bg-alt);
121
+ border-radius: var(--radius-xl);
122
+ padding: var(--spacing-xl);
123
+ border: 1px solid var(--color-border);
124
+ transition: all var(--transition-base);
125
+ text-decoration: none;
126
+ display: flex;
127
+ flex-direction: column;
128
+ }
129
+
130
+ .position-card:hover {
131
+ border-color: var(--color-secondary);
132
+ box-shadow: var(--shadow-xl);
133
+ transform: translateY(-4px);
134
+ }
135
+
136
+ .position-card__header {
137
+ display: flex;
138
+ align-items: flex-start;
139
+ justify-content: space-between;
140
+ gap: var(--spacing-sm);
141
+ margin-bottom: var(--spacing-md);
142
+ }
143
+
144
+ .position-card__title {
145
+ font-family: var(--font-display);
146
+ font-size: 1.25rem;
147
+ font-weight: 600;
148
+ color: var(--color-primary);
149
+ margin: 0;
150
+ line-height: 1.3;
151
+ }
152
+
153
+ .badge {
154
+ display: inline-flex;
155
+ align-items: center;
156
+ padding: var(--spacing-xs) var(--spacing-sm);
157
+ font-size: 0.75rem;
158
+ font-weight: 600;
159
+ letter-spacing: 0.08em;
160
+ text-transform: uppercase;
161
+ border-radius: var(--radius-sm);
162
+ flex-shrink: 0;
163
+ }
164
+
165
+ .badge-accent {
166
+ background: var(--color-accent);
167
+ color: white;
168
+ }
169
+
170
+ .position-card__description {
171
+ font-size: 0.9375rem;
172
+ color: var(--color-text-muted);
173
+ line-height: 1.6;
174
+ margin-bottom: var(--spacing-lg);
175
+ flex: 1;
176
+ }
177
+
178
+ .position-card__footer {
179
+ margin-top: auto;
180
+ }
181
+
182
+ .position-card__cta {
183
+ font-size: 0.875rem;
184
+ font-weight: 600;
185
+ color: var(--color-secondary);
186
+ transition: color var(--transition-fast);
187
+ }
188
+
189
+ .position-card:hover .position-card__cta {
190
+ color: var(--color-accent);
191
+ }
192
+
193
+ /* Coming Soon */
194
+ .positions-coming-soon {
195
+ text-align: center;
196
+ padding: var(--spacing-4xl) var(--spacing-lg);
197
+ background: var(--color-bg-alt);
198
+ border-radius: var(--radius-2xl);
199
+ border: 1px dashed var(--color-border);
200
+ margin-bottom: var(--spacing-3xl);
201
+ }
202
+
203
+ .placeholder-icon {
204
+ margin-bottom: var(--spacing-lg);
205
+ display: flex;
206
+ justify-content: center;
207
+ align-items: center;
208
+ }
209
+
210
+ .positions-coming-soon h3 {
211
+ font-family: var(--font-display);
212
+ font-size: 1.75rem;
213
+ font-weight: 600;
214
+ color: var(--color-primary);
215
+ margin-bottom: var(--spacing-md);
216
+ }
217
+
218
+ .positions-coming-soon p {
219
+ font-size: 1.0625rem;
220
+ color: var(--color-text-muted);
221
+ margin: 0;
222
+ }
223
+
224
+ /* Contact Section */
225
+ .positions-contact {
226
+ text-align: center;
227
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light) 100%);
228
+ color: white;
229
+ border-radius: var(--radius-2xl);
230
+ padding: var(--spacing-3xl) var(--spacing-xl);
231
+ }
232
+
233
+ .positions-contact h3 {
234
+ font-family: var(--font-display);
235
+ font-size: 1.75rem;
236
+ font-weight: 700;
237
+ color: white;
238
+ margin-bottom: var(--spacing-md);
239
+ }
240
+
241
+ .positions-contact p {
242
+ font-size: 1.0625rem;
243
+ opacity: 0.9;
244
+ max-width: 600px;
245
+ margin: 0 auto var(--spacing-xl);
246
+ }
247
+
248
+ .positions-contact .btn {
249
+ background: white;
250
+ color: var(--color-primary);
251
+ border-radius: var(--radius-full); /* pill */
252
+ padding: 0.75rem 1.25rem;
253
+ box-shadow: 0 6px 18px rgba(2,6,23,0.08);
254
+ }
255
+
256
+ .positions-contact .btn:hover {
257
+ background: var(--color-accent);
258
+ color: white;
259
+ }
260
+
261
+ @media (max-width: 768px) {
262
+ .positions-grid {
263
+ grid-template-columns: 1fr;
264
+ }
265
+ }
266
+ </style>