@mundogamernetwork/shared-ui 1.0.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.
Files changed (87) hide show
  1. package/README.md +283 -0
  2. package/components/PressKit/AssetGallery.vue +349 -0
  3. package/components/PressKit/Awards.vue +100 -0
  4. package/components/PressKit/Credits.vue +78 -0
  5. package/components/PressKit/FactSheet.vue +204 -0
  6. package/components/PressKit/Hero.vue +143 -0
  7. package/components/PressKit/Quotes.vue +80 -0
  8. package/components/PressKit/VideoPlayer.vue +134 -0
  9. package/components/checkout/MgCartItemList.vue +214 -0
  10. package/components/checkout/MgCartSummary.vue +204 -0
  11. package/components/checkout/MgCheckoutSidebar.vue +230 -0
  12. package/components/checkout/MgGuestEmailForm.vue +97 -0
  13. package/components/checkout/MgPaymentMethodSelector.vue +162 -0
  14. package/components/checkout/MgPixQRCode.vue +222 -0
  15. package/components/indie-wall/IndieWallLeaderboard.vue +208 -0
  16. package/components/indie-wall/MuralCanvas.vue +481 -0
  17. package/components/indie-wall/StepBlock.vue +314 -0
  18. package/components/indie-wall/StepCustomize.vue +530 -0
  19. package/components/indie-wall/StepGoal.vue +169 -0
  20. package/components/indie-wall/StepPackage.vue +145 -0
  21. package/components/indie-wall/StepPay.vue +209 -0
  22. package/components/indie-wall/SupportStepper.vue +372 -0
  23. package/components/invoices/MgInvoiceDownload.vue +50 -0
  24. package/components/pricing/MgBillingToggle.vue +74 -0
  25. package/components/pricing/MgPricingCard.vue +245 -0
  26. package/components/ui/Header/MgMessageCard.vue +147 -0
  27. package/components/ui/Header/MgMessageModal.vue +414 -0
  28. package/components/ui/Header/MgNotificationCard.vue +200 -0
  29. package/components/ui/Header/MgNotificationsModal.vue +125 -0
  30. package/components/ui/MgAnnouncementBanner.vue +147 -0
  31. package/components/ui/MgBanners.vue +23 -0
  32. package/components/ui/MgHeaderComponent.vue +283 -0
  33. package/components/ui/MgHeaderUIConfig.vue +225 -0
  34. package/components/ui/MgHeaderUIUser.vue +301 -0
  35. package/components/ui/MgLoginModal.vue +156 -0
  36. package/components/ui/MgPromotionBanner.vue +185 -0
  37. package/composables/useLogout.ts +42 -0
  38. package/composables/useMgCheckout.ts +287 -0
  39. package/composables/useMgUserNotifications.ts +122 -0
  40. package/composables/usePaymentMethods.ts +75 -0
  41. package/composables/useSubscription.ts +163 -0
  42. package/middleware/auth.global.ts +40 -0
  43. package/nuxt.config.ts +31 -0
  44. package/package.json +40 -0
  45. package/pages/[slug]/index.vue +112 -0
  46. package/pages/about.vue +133 -0
  47. package/pages/blog.vue +430 -0
  48. package/pages/careers.vue +329 -0
  49. package/pages/contact.vue +339 -0
  50. package/pages/faq.vue +317 -0
  51. package/pages/health-check.vue +20 -0
  52. package/pages/icons.vue +58 -0
  53. package/pages/magazine/[slug].vue +209 -0
  54. package/pages/magazine/index.vue +267 -0
  55. package/pages/media-kit/[slug].vue +625 -0
  56. package/pages/mural/[slug].vue +1058 -0
  57. package/pages/partners.vue +290 -0
  58. package/pages/press.vue +237 -0
  59. package/pages/presskit/[slug].vue +191 -0
  60. package/pages/roadmap.vue +355 -0
  61. package/pages/status.vue +199 -0
  62. package/pages/team.vue +266 -0
  63. package/pages/wall/[slug].vue +11 -0
  64. package/plugins/auth.client.ts +17 -0
  65. package/plugins/echo.client.ts +132 -0
  66. package/services/authService.ts +95 -0
  67. package/services/chatService.ts +53 -0
  68. package/services/contactService.ts +35 -0
  69. package/services/documentService.ts +16 -0
  70. package/services/httpService.ts +95 -0
  71. package/services/indieWallService.ts +174 -0
  72. package/services/institutionalService.ts +248 -0
  73. package/services/mediaKitService.ts +51 -0
  74. package/services/notificationsService.ts +20 -0
  75. package/services/pressKitService.ts +55 -0
  76. package/stores/announcement.ts +129 -0
  77. package/stores/auth.ts +86 -0
  78. package/stores/chat.ts +150 -0
  79. package/stores/contact.ts +28 -0
  80. package/stores/document.ts +27 -0
  81. package/stores/index.ts +34 -0
  82. package/stores/institutional.ts +231 -0
  83. package/stores/login.ts +27 -0
  84. package/stores/notifications.ts +133 -0
  85. package/stores/promotion.ts +154 -0
  86. package/types/index.ts +135 -0
  87. package/utils/serialize.ts +29 -0
@@ -0,0 +1,100 @@
1
+ <script setup lang="ts">
2
+ interface Award {
3
+ id: number;
4
+ title: string;
5
+ event?: string;
6
+ year?: number;
7
+ url?: string;
8
+ sort_order?: number;
9
+ }
10
+
11
+ interface Props {
12
+ awards: Award[];
13
+ }
14
+
15
+ defineProps<Props>();
16
+ </script>
17
+
18
+ <template>
19
+ <div v-if="awards.length" class="awards-section">
20
+ <h3 class="section-title">{{ $t('press_kit.awards') }}</h3>
21
+ <div class="awards-grid">
22
+ <a
23
+ v-for="award in awards"
24
+ :key="award.id"
25
+ :href="award.url || undefined"
26
+ :target="award.url ? '_blank' : undefined"
27
+ rel="noopener"
28
+ :class="['award-card', { 'no-link': !award.url }]"
29
+ >
30
+ <MGIcon icon="trophy" class="award-icon" />
31
+ <div class="award-info">
32
+ <span class="award-title">{{ award.title }}</span>
33
+ <span v-if="award.event || award.year" class="award-event">
34
+ {{ [award.event, award.year].filter(Boolean).join(' — ') }}
35
+ </span>
36
+ </div>
37
+ </a>
38
+ </div>
39
+ </div>
40
+ </template>
41
+
42
+ <style lang="scss" scoped>
43
+ .awards-section {
44
+ .section-title {
45
+ font-size: 1.2rem;
46
+ font-weight: 700;
47
+ color: #fff;
48
+ margin-bottom: 16px;
49
+ }
50
+
51
+ .awards-grid {
52
+ display: grid;
53
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
54
+ gap: 12px;
55
+ }
56
+
57
+ .award-card {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 14px;
61
+ background: #191B20;
62
+ border: 1px solid #1e2028;
63
+ padding: 16px;
64
+ text-decoration: none;
65
+ transition: background 0.2s, border-color 0.2s;
66
+
67
+ &:not(.no-link):hover {
68
+ background: #22252b;
69
+ border-color: #3a3d45;
70
+ }
71
+
72
+ &.no-link {
73
+ cursor: default;
74
+ }
75
+ }
76
+
77
+ .award-icon {
78
+ color: var(--color-primary, #FDB215);
79
+ font-size: 1.4rem;
80
+ flex-shrink: 0;
81
+ }
82
+
83
+ .award-info {
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: 2px;
87
+ }
88
+
89
+ .award-title {
90
+ font-size: 0.95rem;
91
+ font-weight: 600;
92
+ color: #fff;
93
+ }
94
+
95
+ .award-event {
96
+ font-size: 0.8rem;
97
+ color: #888;
98
+ }
99
+ }
100
+ </style>
@@ -0,0 +1,78 @@
1
+ <script setup lang="ts">
2
+ interface Credit {
3
+ id: number;
4
+ name: string;
5
+ role: string;
6
+ url?: string;
7
+ sort_order?: number;
8
+ }
9
+
10
+ interface Props {
11
+ credits: Credit[];
12
+ }
13
+
14
+ defineProps<Props>();
15
+ </script>
16
+
17
+ <template>
18
+ <div v-if="credits.length" class="credits-section">
19
+ <h3 class="section-title">{{ $t('press_kit.credits') }}</h3>
20
+ <div class="credits-grid">
21
+ <div v-for="credit in credits" :key="credit.id" class="credit-item">
22
+ <span class="credit-role">{{ credit.role }}</span>
23
+ <a v-if="credit.url" :href="credit.url" target="_blank" rel="noopener" class="credit-name link">
24
+ {{ credit.name }}
25
+ </a>
26
+ <span v-else class="credit-name">{{ credit.name }}</span>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <style lang="scss" scoped>
33
+ .credits-section {
34
+ .section-title {
35
+ font-size: 1.2rem;
36
+ font-weight: 700;
37
+ color: #fff;
38
+ margin-bottom: 16px;
39
+ }
40
+
41
+ .credits-grid {
42
+ display: grid;
43
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
44
+ gap: 16px;
45
+ }
46
+
47
+ .credit-item {
48
+ background: #191B20;
49
+ border: 1px solid #1e2028;
50
+ padding: 16px;
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 4px;
54
+ }
55
+
56
+ .credit-role {
57
+ font-size: 0.75rem;
58
+ color: #888;
59
+ text-transform: uppercase;
60
+ letter-spacing: 0.5px;
61
+ }
62
+
63
+ .credit-name {
64
+ font-size: 0.95rem;
65
+ color: #fff;
66
+ font-weight: 500;
67
+
68
+ &.link {
69
+ color: #64D8FF;
70
+ text-decoration: none;
71
+
72
+ &:hover {
73
+ text-decoration: underline;
74
+ }
75
+ }
76
+ }
77
+ }
78
+ </style>
@@ -0,0 +1,204 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ kit: Record<string, any>;
4
+ }
5
+
6
+ const props = defineProps<Props>();
7
+
8
+ const facts = computed(() => {
9
+ const items: { label: string; value: string }[] = [];
10
+ const k = props.kit;
11
+
12
+ if (k.developer) items.push({ label: 'press_kit.developer', value: k.developer });
13
+ if (k.publisher) items.push({ label: 'press_kit.publisher_label', value: k.publisher });
14
+ if (k.release_date) items.push({ label: 'press_kit.release_date', value: k.release_date });
15
+ if (k.release_status) items.push({ label: 'press_kit.release_status', value: formatStatus(k.release_status) });
16
+ if (k.price) items.push({ label: 'press_kit.price', value: k.price });
17
+ if (k.monetization) items.push({ label: 'press_kit.monetization', value: formatMonetization(k.monetization) });
18
+ if (k.website_url) items.push({ label: 'press_kit.website', value: k.website_url });
19
+ if (k.press_contact_email) items.push({ label: 'press_kit.press_contact', value: k.press_contact_email });
20
+
21
+ return items;
22
+ });
23
+
24
+ const socialLinks = computed(() => {
25
+ if (!props.kit.social_links) return [];
26
+ const links = props.kit.social_links;
27
+ return Object.entries(links).filter(([, v]) => v).map(([key, url]) => ({ platform: key, url }));
28
+ });
29
+
30
+ const storeLinks = computed(() => {
31
+ const links: { platform: string; url: string }[] = [];
32
+ if (props.kit.steam_url) links.push({ platform: 'Steam', url: props.kit.steam_url });
33
+ if (props.kit.epic_url) links.push({ platform: 'Epic Games', url: props.kit.epic_url });
34
+ return links;
35
+ });
36
+
37
+ function formatStatus(status: string) {
38
+ return status.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
39
+ }
40
+
41
+ function formatMonetization(m: string) {
42
+ return m.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
43
+ }
44
+ </script>
45
+
46
+ <template>
47
+ <div class="fact-sheet">
48
+ <h3 class="section-title">{{ $t('press_kit.fact_sheet') }}</h3>
49
+
50
+ <div class="facts-list">
51
+ <div v-for="fact in facts" :key="fact.label" class="fact-item">
52
+ <span class="fact-label">{{ $t(fact.label) }}</span>
53
+ <span v-if="fact.value.startsWith('http')" class="fact-value">
54
+ <a :href="fact.value" target="_blank" rel="noopener">{{ fact.value }}</a>
55
+ </span>
56
+ <span v-else-if="fact.value.includes('@')" class="fact-value">
57
+ <a :href="`mailto:${fact.value}`">{{ fact.value }}</a>
58
+ </span>
59
+ <span v-else class="fact-value">{{ fact.value }}</span>
60
+ </div>
61
+ </div>
62
+
63
+ <div v-if="kit.key_features?.length" class="key-features">
64
+ <h4>{{ $t('press_kit.key_features') }}</h4>
65
+ <ul>
66
+ <li v-for="(feature, idx) in kit.key_features" :key="idx">{{ feature }}</li>
67
+ </ul>
68
+ </div>
69
+
70
+ <div v-if="storeLinks.length" class="store-links">
71
+ <h4>{{ $t('press_kit.available_on') }}</h4>
72
+ <div class="links-row">
73
+ <a v-for="link in storeLinks" :key="link.platform" :href="link.url" target="_blank" rel="noopener" class="store-badge">
74
+ {{ link.platform }}
75
+ </a>
76
+ </div>
77
+ </div>
78
+
79
+ <div v-if="socialLinks.length" class="social-links">
80
+ <h4>{{ $t('press_kit.social_media') }}</h4>
81
+ <div class="links-row">
82
+ <a v-for="link in socialLinks" :key="link.platform" :href="link.url as string" target="_blank" rel="noopener" class="social-badge">
83
+ {{ link.platform }}
84
+ </a>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </template>
89
+
90
+ <style lang="scss" scoped>
91
+ .fact-sheet {
92
+ background: #191B20;
93
+ border: 1px solid #1e2028;
94
+ padding: 24px;
95
+
96
+ .section-title {
97
+ font-size: 1.2rem;
98
+ font-weight: 700;
99
+ color: var(--color-primary, #FDB215);
100
+ margin-bottom: 20px;
101
+ }
102
+
103
+ .facts-list {
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: 12px;
107
+ margin-bottom: 24px;
108
+ }
109
+
110
+ .fact-item {
111
+ display: flex;
112
+ flex-direction: column;
113
+ gap: 2px;
114
+ }
115
+
116
+ .fact-label {
117
+ font-size: 0.75rem;
118
+ color: #888;
119
+ text-transform: uppercase;
120
+ letter-spacing: 0.5px;
121
+ }
122
+
123
+ .fact-value {
124
+ font-size: 0.95rem;
125
+ color: #fff;
126
+
127
+ a {
128
+ color: #64D8FF;
129
+ text-decoration: none;
130
+ word-break: break-all;
131
+
132
+ &:hover {
133
+ text-decoration: underline;
134
+ }
135
+ }
136
+ }
137
+
138
+ .key-features {
139
+ margin-bottom: 24px;
140
+
141
+ h4 {
142
+ font-size: 0.9rem;
143
+ font-weight: 600;
144
+ color: #fff;
145
+ margin-bottom: 8px;
146
+ }
147
+
148
+ ul {
149
+ list-style: none;
150
+ padding: 0;
151
+ margin: 0;
152
+
153
+ li {
154
+ padding: 6px 0;
155
+ color: #ccc;
156
+ font-size: 0.9rem;
157
+ border-bottom: 1px solid #2a2d35;
158
+
159
+ &:last-child {
160
+ border-bottom: none;
161
+ }
162
+
163
+ &::before {
164
+ content: '•';
165
+ color: var(--color-primary, #FDB215);
166
+ margin-right: 8px;
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ .store-links, .social-links {
173
+ margin-bottom: 16px;
174
+
175
+ h4 {
176
+ font-size: 0.9rem;
177
+ font-weight: 600;
178
+ color: #fff;
179
+ margin-bottom: 8px;
180
+ }
181
+ }
182
+
183
+ .links-row {
184
+ display: flex;
185
+ flex-wrap: wrap;
186
+ gap: 8px;
187
+ }
188
+
189
+ .store-badge, .social-badge {
190
+ display: inline-block;
191
+ padding: 6px 14px;
192
+ background: #2a2d35;
193
+ border: 1px solid #3a3d45;
194
+ color: #fff;
195
+ font-size: 0.85rem;
196
+ text-decoration: none;
197
+ transition: background 0.2s;
198
+
199
+ &:hover {
200
+ background: #3a3d45;
201
+ }
202
+ }
203
+ }
204
+ </style>
@@ -0,0 +1,143 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ name: string;
4
+ tagline?: string;
5
+ heroImageUrl?: string;
6
+ logoUrl?: string;
7
+ trailerUrl?: string;
8
+ }
9
+
10
+ const props = defineProps<Props>();
11
+
12
+ const showTrailer = ref(false);
13
+
14
+ const youtubeEmbedUrl = computed(() => {
15
+ if (!props.trailerUrl) return null;
16
+ const match = props.trailerUrl.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
17
+ if (match) return `https://www.youtube.com/embed/${match[1]}?autoplay=1`;
18
+ return props.trailerUrl;
19
+ });
20
+ </script>
21
+
22
+ <template>
23
+ <div class="press-kit-hero">
24
+ <div class="hero-banner" :style="heroImageUrl ? { backgroundImage: `url(${heroImageUrl})` } : {}">
25
+ <div class="hero-overlay">
26
+ <img v-if="logoUrl" :src="logoUrl" :alt="name" class="hero-logo" />
27
+ <h1 v-else class="hero-title">{{ name }}</h1>
28
+ <p v-if="tagline" class="hero-tagline">{{ tagline }}</p>
29
+ <button v-if="trailerUrl" class="btn-trailer" @click="showTrailer = true">
30
+ <MGIcon icon="play" /> {{ $t('press_kit.watch_trailer') }}
31
+ </button>
32
+ </div>
33
+ </div>
34
+
35
+ <div v-if="showTrailer" class="trailer-modal" @click.self="showTrailer = false">
36
+ <div class="trailer-wrapper">
37
+ <button class="btn-close-trailer" @click="showTrailer = false">&times;</button>
38
+ <iframe
39
+ v-if="youtubeEmbedUrl"
40
+ :src="youtubeEmbedUrl"
41
+ frameborder="0"
42
+ allow="autoplay; encrypted-media"
43
+ allowfullscreen
44
+ />
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <style lang="scss" scoped>
51
+ .press-kit-hero {
52
+ .hero-banner {
53
+ position: relative;
54
+ height: 400px;
55
+ background-size: cover;
56
+ background-position: center;
57
+ background-color: #191B20;
58
+ overflow: hidden;
59
+ }
60
+
61
+ .hero-overlay {
62
+ position: absolute;
63
+ inset: 0;
64
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.3) 100%);
65
+ display: flex;
66
+ flex-direction: column;
67
+ justify-content: flex-end;
68
+ align-items: flex-start;
69
+ padding: 40px;
70
+ }
71
+
72
+ .hero-logo {
73
+ max-height: 80px;
74
+ max-width: 300px;
75
+ object-fit: contain;
76
+ margin-bottom: 12px;
77
+ }
78
+
79
+ .hero-title {
80
+ font-size: 2.5rem;
81
+ font-weight: 700;
82
+ color: #fff;
83
+ margin-bottom: 8px;
84
+ }
85
+
86
+ .hero-tagline {
87
+ font-size: 1.1rem;
88
+ color: #ccc;
89
+ margin-bottom: 16px;
90
+ }
91
+
92
+ .btn-trailer {
93
+ background: var(--color-primary, #FDB215);
94
+ color: #13161C;
95
+ border: none;
96
+ padding: 10px 24px;
97
+ font-weight: 600;
98
+ font-size: 0.95rem;
99
+ cursor: pointer;
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 8px;
103
+ transition: opacity 0.2s;
104
+
105
+ &:hover {
106
+ opacity: 0.85;
107
+ }
108
+ }
109
+
110
+ .trailer-modal {
111
+ position: fixed;
112
+ inset: 0;
113
+ background: rgba(0, 0, 0, 0.9);
114
+ z-index: 9999;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ }
119
+
120
+ .trailer-wrapper {
121
+ position: relative;
122
+ width: 80vw;
123
+ max-width: 900px;
124
+ aspect-ratio: 16 / 9;
125
+
126
+ iframe {
127
+ width: 100%;
128
+ height: 100%;
129
+ }
130
+ }
131
+
132
+ .btn-close-trailer {
133
+ position: absolute;
134
+ top: -40px;
135
+ right: 0;
136
+ background: none;
137
+ border: none;
138
+ color: #fff;
139
+ font-size: 2rem;
140
+ cursor: pointer;
141
+ }
142
+ }
143
+ </style>
@@ -0,0 +1,80 @@
1
+ <script setup lang="ts">
2
+ interface Quote {
3
+ id: number;
4
+ quote: string;
5
+ source: string;
6
+ source_url?: string;
7
+ sort_order?: number;
8
+ }
9
+
10
+ interface Props {
11
+ quotes: Quote[];
12
+ }
13
+
14
+ defineProps<Props>();
15
+ </script>
16
+
17
+ <template>
18
+ <div v-if="quotes.length" class="quotes-section">
19
+ <h3 class="section-title">{{ $t('press_kit.quotes') }}</h3>
20
+ <div class="quotes-list">
21
+ <blockquote v-for="q in quotes" :key="q.id" class="quote-card">
22
+ <p class="quote-text">&ldquo;{{ q.quote }}&rdquo;</p>
23
+ <footer class="quote-source">
24
+ &mdash;
25
+ <a v-if="q.source_url" :href="q.source_url" target="_blank" rel="noopener">{{ q.source }}</a>
26
+ <span v-else>{{ q.source }}</span>
27
+ </footer>
28
+ </blockquote>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <style lang="scss" scoped>
34
+ .quotes-section {
35
+ .section-title {
36
+ font-size: 1.2rem;
37
+ font-weight: 700;
38
+ color: #fff;
39
+ margin-bottom: 16px;
40
+ }
41
+
42
+ .quotes-list {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 16px;
46
+ }
47
+
48
+ .quote-card {
49
+ background: #191B20;
50
+ border-left: 3px solid var(--color-primary, #FDB215);
51
+ border-top: 1px solid #1e2028;
52
+ border-right: 1px solid #1e2028;
53
+ border-bottom: 1px solid #1e2028;
54
+ padding: 20px 24px;
55
+ margin: 0;
56
+ }
57
+
58
+ .quote-text {
59
+ font-size: 1rem;
60
+ color: #ddd;
61
+ line-height: 1.6;
62
+ font-style: italic;
63
+ margin-bottom: 12px;
64
+ }
65
+
66
+ .quote-source {
67
+ font-size: 0.85rem;
68
+ color: #888;
69
+
70
+ a {
71
+ color: #64D8FF;
72
+ text-decoration: none;
73
+
74
+ &:hover {
75
+ text-decoration: underline;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ </style>