@mundogamernetwork/shared-ui 1.1.44 → 1.1.46

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.
@@ -0,0 +1,125 @@
1
+ <script setup lang="ts">
2
+ // Public, shareable standalone key-campaign page. Reached from broadcast links
3
+ // (Discord/Telegram/WhatsApp) and from anywhere — no need to enter a dashboard.
4
+ // Anyone can view; requesting a key uses KeyBrowser's hybrid auth (it redirects
5
+ // to login on 401 and returns).
6
+ definePageMeta({ layout: 'blank' });
7
+
8
+ const route = useRoute();
9
+ const slug = route.params.slug as string;
10
+
11
+ const runtimeConfig = useRuntimeConfig();
12
+ const cfg = (runtimeConfig.public?.mgSharedUi || {}) as Record<string, any>;
13
+ const apiBase: string =
14
+ cfg.apiBaseURL ||
15
+ (runtimeConfig.public as any)?.apiBaseURL ||
16
+ '';
17
+
18
+ const SUPPORTED_LOCALES = ['en', 'pt-BR', 'es', 'de', 'ro', 'ar-AE'];
19
+ const routePath = route.path.split('/');
20
+ const lang = SUPPORTED_LOCALES.find(l => routePath.includes(l)) || 'en';
21
+
22
+ const { data: keyData, error: fetchError, pending } = await useAsyncData(
23
+ `public-key-${slug}`,
24
+ () => $fetch<any>(`${apiBase}/public/keys/${slug}`, { params: { lang } }),
25
+ { lazy: false },
26
+ );
27
+
28
+ const key = computed(() => keyData.value?.data || keyData.value || null);
29
+ const loading = pending;
30
+ const error = computed(() => !!fetchError.value || (!pending.value && !key.value));
31
+
32
+ const restriction = computed(() => {
33
+ const k = key.value;
34
+ if (!k) return '';
35
+ if (k.is_exclusive) return 'Exclusiva para streamers oficiais';
36
+ if (k.is_tier_locked && k.access_tier && k.access_tier !== 'free') return `Exclusiva para o plano ${String(k.access_tier).toUpperCase()} ou superior`;
37
+ if (k.streamer_type_id === 2) return 'Exclusiva para streamers oficiais';
38
+ if (k.streamer_type_id === 3) return 'Exclusiva para afiliados';
39
+ return '';
40
+ });
41
+ </script>
42
+
43
+ <template>
44
+ <div class="kc">
45
+ <div v-if="loading" class="kc__state">Carregando…</div>
46
+ <div v-else-if="error" class="kc__state">Campanha não encontrada.</div>
47
+
48
+ <template v-else>
49
+ <div class="kc__hero">
50
+ <img v-if="key.cover_src" :src="key.cover_src" :alt="key.name" class="kc__cover">
51
+ <div class="kc__hero-body">
52
+ <h1 class="kc__title">{{ key.name }}</h1>
53
+ <div v-if="restriction" class="kc__badge">🔒 {{ restriction }}</div>
54
+ <div class="kc__avail">
55
+ <span v-if="key.available_count > 0">{{ key.available_count }} chave(s) pra resgatar</span>
56
+ <span v-else>Chaves esgotadas</span>
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="kc__browser">
62
+ <KeyBrowser
63
+ requester-context="user"
64
+ :game-id="key.game?.id || key.game_id"
65
+ />
66
+ </div>
67
+ </template>
68
+ </div>
69
+ </template>
70
+
71
+ <style scoped lang="scss">
72
+ .kc {
73
+ max-width: 960px;
74
+ margin: 0 auto;
75
+ padding: 24px 16px 64px;
76
+
77
+ &__state {
78
+ padding: 80px 0;
79
+ text-align: center;
80
+ color: var(--text-secondary, #888);
81
+ }
82
+
83
+ &__hero {
84
+ display: flex;
85
+ gap: 20px;
86
+ align-items: flex-start;
87
+ margin-bottom: 28px;
88
+ flex-wrap: wrap;
89
+ }
90
+
91
+ &__cover {
92
+ width: 260px;
93
+ max-width: 100%;
94
+ height: auto;
95
+ object-fit: cover;
96
+ border: 1px solid var(--border-color, #2a2a2a);
97
+ }
98
+
99
+ &__hero-body { flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: 10px; }
100
+
101
+ &__title {
102
+ font-size: 1.6rem;
103
+ font-weight: 700;
104
+ color: var(--text-primary, #fff);
105
+ margin: 0;
106
+ }
107
+
108
+ &__badge {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: 6px;
112
+ width: fit-content;
113
+ padding: 4px 10px;
114
+ font-size: 0.8rem;
115
+ font-weight: 600;
116
+ color: #d297ff;
117
+ border: 1px solid rgba(210, 151, 255, 0.35);
118
+ background: rgba(210, 151, 255, 0.12);
119
+ }
120
+
121
+ &__avail { font-size: 0.9rem; color: var(--text-secondary, #aaa); }
122
+
123
+ &__browser { margin-top: 8px; }
124
+ }
125
+ </style>
@@ -52,7 +52,7 @@ const locale = useNuxtApp().$i18n.locale;
52
52
  const slug = computed(() => route.params.slug as string);
53
53
 
54
54
  const magazineStore = useMagazineStore();
55
- const { magazine, magazines, magazinePages } = magazineStore;
55
+ const { magazine, magazines, magazinePages } = storeToRefs(magazineStore);
56
56
  const institutionalStore = useInstitutionalStore();
57
57
  const { language } = institutionalStore;
58
58
 
@@ -124,7 +124,7 @@ function restoreReadingProgress() {
124
124
  return;
125
125
  }
126
126
 
127
- const totalPages = magazinePages.data?.length ?? 0;
127
+ const totalPages = magazinePages.value.data?.length ?? 0;
128
128
  if (!totalPages) {
129
129
  startPage.value = 0;
130
130
  return;
@@ -148,15 +148,15 @@ const showDownloadGateMessage = ref("");
148
148
 
149
149
  // Flipbook pages mapped for MgnFlipbook
150
150
  const flipbookPages = computed<PageData[]>(() => {
151
- if (!magazinePages.data || magazinePages.data.length === 0) return [];
152
- return magazinePages.data.map((page: MagazinePage) => ({
151
+ if (!magazinePages.value.data || magazinePages.value.data.length === 0) return [];
152
+ return magazinePages.value.data.map((page: MagazinePage) => ({
153
153
  index: page.page_number - 1,
154
154
  thumbnailUrl: page.thumbnail_url,
155
155
  imageUrl: page.image_url,
156
156
  hiresUrl: page.hires_url,
157
157
  hotspots: getMagazinePageHotspots({
158
158
  page,
159
- flipbookCode: magazine.data?.flipbook_code,
159
+ flipbookCode: magazine.value.data?.flipbook_code,
160
160
  }),
161
161
  }));
162
162
  });
@@ -164,14 +164,14 @@ const flipbookPages = computed<PageData[]>(() => {
164
164
  // Determine if lead capture modal should show
165
165
  const requiresLeadCapture = computed(() => {
166
166
  if (leadCaptured.value) return false;
167
- if (!magazinePages.meta) return false;
168
- return magazinePages.meta.requires_lead || magazinePages.meta.capture_leads;
167
+ if (!magazinePages.value.meta) return false;
168
+ return magazinePages.value.meta.requires_lead || magazinePages.value.meta.capture_leads;
169
169
  });
170
170
 
171
171
  async function getMagazineData() {
172
172
  try {
173
173
  await magazineStore.getMagazine(slug.value);
174
- status.value = magazine.status;
174
+ status.value = magazine.value.status;
175
175
  } catch (error) {
176
176
  console.error("Error fetching magazine:", error);
177
177
  }
@@ -195,8 +195,8 @@ async function loadFlipbookPages() {
195
195
  }
196
196
 
197
197
  function downloadPdf() {
198
- if (magazine.data.pdf_url_src) {
199
- window.open(magazine.data.pdf_url_src, "_blank");
198
+ if (magazine.value.data.pdf_url_src) {
199
+ window.open(magazine.value.data.pdf_url_src, "_blank");
200
200
  magazineStore.trackAnalytic(slug.value, "download");
201
201
  }
202
202
  }
@@ -210,13 +210,13 @@ function downloadPdf() {
210
210
  */
211
211
  function handleFlipbookDownload() {
212
212
  // Gate 1: Premium magazine - need active subscription
213
- if (magazine.data.premium === true) {
213
+ if (magazine.value.data.premium === true) {
214
214
  showDownloadGateMessage.value = "premium";
215
215
  return;
216
216
  }
217
217
 
218
218
  // Gate 2: Non-public magazine - must be registered/signed in
219
- if (magazine.data.public === false && signedIn.value === false) {
219
+ if (magazine.value.data.public === false && signedIn.value === false) {
220
220
  showDownloadGateMessage.value = "signin";
221
221
  return;
222
222
  }
@@ -307,7 +307,7 @@ async function fetchMagazine() {
307
307
  await magazineStore.fetchMagazine({
308
308
  filter: {
309
309
  status: 1,
310
- edition_number: magazine.data.edition_number,
310
+ edition_number: magazine.value.data.edition_number,
311
311
  platform_id: import.meta.env.VITE_SYSTEM_ID,
312
312
  },
313
313
  sort: "id",
@@ -331,16 +331,16 @@ async function fetchLanguage() {
331
331
  }
332
332
 
333
333
  function filterLanguages() {
334
- if (magazine.data && magazines.data && magazines.data.length > 0) {
335
- languageId.value = magazine.data.language.id;
336
- const languageIds = magazines.data.map(
334
+ if (magazine.value.data && magazines.value.data && magazines.value.data.length > 0) {
335
+ languageId.value = magazine.value.data.language.id;
336
+ const languageIds = magazines.value.data.map(
337
337
  (item: MagazineItem) => item.language.id,
338
338
  );
339
339
  languages.value = language.data.filter((language: LanguageItem) =>
340
340
  languageIds.includes(language.id),
341
341
  );
342
342
  languages.value.forEach((language: LanguageItem) => {
343
- const magazineItem = magazines.data.find(
343
+ const magazineItem = magazines.value.data.find(
344
344
  (item: MagazineItem) => item.language.id === language.id,
345
345
  );
346
346
  if (magazineItem) {
@@ -1,275 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { trackPressKitEvent } from '../../services/pressKitService';
3
-
4
- definePageMeta({ layout: 'blank' });
5
-
6
- const route = useRoute();
7
- const slug = route.params.slug as string;
8
-
9
- const runtimeConfig = useRuntimeConfig();
10
- const cfg = (runtimeConfig.public?.mgSharedUi || {}) as Record<string, any>;
11
- // Consuming apps may set apiBaseURL at public root (agency/community/tv pattern)
12
- // or inside mgSharedUi. pressKitApiUrl overrides both (e.g. community points to agency API).
13
- const apiBase: string =
14
- cfg.pressKitApiUrl ||
15
- (runtimeConfig.public as any)?.pressKitApiUrl ||
16
- cfg.apiBaseURL ||
17
- (runtimeConfig.public as any)?.apiBaseURL ||
18
- '';
19
-
20
- const agencyBase: string =
21
- cfg.agencyBaseUrl ||
22
- (runtimeConfig.public as any)?.agencyBaseUrl ||
23
- '';
24
-
25
- const SUPPORTED_LOCALES = ['en', 'pt-BR', 'es', 'de', 'ro', 'ar-AE'];
26
- const routePath = route.path.split('/');
27
- const lang = SUPPORTED_LOCALES.find(l => routePath.includes(l)) || 'en';
28
-
29
- const { data: kitData, error: fetchError, pending } = await useAsyncData(
30
- `press-kit-${slug}`,
31
- () => $fetch<any>(`${apiBase}/public/press-kits/${slug}`, {
32
- params: { include: 'assets,videos,credits,awards,quotes', lang },
33
- }),
34
- { lazy: false },
35
- );
36
-
37
- const kit = computed(() => kitData.value?.data || kitData.value || null);
38
- const loading = pending;
39
- const error = computed(() => !!fetchError.value || (!pending.value && !kit.value));
40
-
41
- onMounted(() => {
42
- if (kit.value) {
43
- trackPressKitEvent(slug, {
44
- event_type: 'view',
45
- referrer: document.referrer || undefined,
46
- }).catch(() => {});
47
- }
48
- });
49
-
50
- // Fractal include shape: relation -> { data: [...] }
51
- const list = (rel: any) => rel?.data || rel || [];
52
- const assets = computed(() => list(kit.value?.assets));
53
- const videos = computed(() => list(kit.value?.videos));
54
- const awards = computed(() => list(kit.value?.awards));
55
- const quotes = computed(() => list(kit.value?.quotes));
56
- const team = computed(() => list(kit.value?.credits));
57
- const features = computed(() => kit.value?.key_features || []);
58
- // New, optional blocks (rendered only when the API provides them)
59
- const articles = computed(() => list(kit.value?.articles));
60
- const coverage = computed(() => list(kit.value?.coverage));
61
- const showcases = computed(() => list(kit.value?.showcases));
62
- const hasEcosystem = computed(() => showcases.value.length || kit.value?.ventures_url || kit.value?.magazine_feature || kit.value?.community_url);
63
-
64
- const screenshots = computed(() => assets.value.filter((a: any) => ['screenshot', 'artwork', 'gif'].includes(a.type)));
65
- const brandAssets = computed(() => assets.value.filter((a: any) => ['logo', 'document'].includes(a.type)));
66
-
67
- const tpl = computed(() => kit.value?.theme || null);
68
- const layout = computed(() => kit.value?.layout || 'classic');
69
- const allowDownload = computed(() => kit.value?.allow_asset_download !== false);
70
-
71
- const socials = computed(() => {
72
- const s = kit.value?.social_links || {};
73
- return Object.entries(s).filter(([, v]) => v).map(([k, v]) => ({ platform: k, url: v as string }));
74
- });
75
-
76
- const facts = computed(() => {
77
- const k = kit.value || {};
78
- const out: { label: string; value: string; accent?: boolean; href?: string }[] = [];
79
- if (k.developer) out.push({ label: 'kit.press.developer', value: k.developer });
80
- if (k.publisher) out.push({ label: 'kit.press.publisher', value: k.publisher });
81
- if (k.release_date_tba) out.push({ label: 'kit.press.release', value: 'TBA', accent: true });
82
- else if (k.release_date) out.push({ label: 'kit.press.release', value: k.release_date, accent: true });
83
- if (k.release_status) out.push({ label: 'kit.press.status', value: String(k.release_status).replace(/_/g, ' ') });
84
- if (k.price) out.push({ label: 'kit.press.price', value: `${k.price}${k.currency ? ' ' + k.currency : ''}` });
85
- if (k.website_url) out.push({ label: 'kit.press.website', value: k.website_url, accent: true, href: k.website_url });
86
- if (k.steam_url) out.push({ label: 'Steam', value: k.steam_url, href: k.steam_url });
87
- if (k.press_contact_email) out.push({ label: 'kit.press.contact', value: k.press_contact_email, href: `mailto:${k.press_contact_email}` });
88
- return out;
89
- });
90
-
91
- function track(type: 'click' | 'download' | 'contact') {
92
- trackPressKitEvent(slug, { event_type: type, referrer: document.referrer || undefined }).catch(() => {});
93
- }
94
-
95
- const requestUrl = useRequestURL();
96
- useHead(() => ({
97
- title: kit.value ? `${kit.value.name} — Press Kit` : 'Press Kit',
98
- meta: [
99
- { name: 'description', content: kit.value?.tagline || '' },
100
- { property: 'og:title', content: kit.value?.name || 'Press Kit' },
101
- { property: 'og:description', content: kit.value?.tagline || '' },
102
- { property: 'og:image', content: kit.value?.hero_image_url || kit.value?.logo_url || '' },
103
- { property: 'og:type', content: 'article' },
104
- { property: 'og:url', content: requestUrl.href },
105
- ],
106
- }));
2
+ // Redirect /presskit/[slug] /presskit/game/[slug] (canonical press kit route)
3
+ definePageMeta({ layout: 'blank' })
4
+ const route = useRoute()
5
+ navigateTo(`/presskit/game/${route.params.slug}`, { redirectCode: 301 })
107
6
  </script>
108
-
109
- <template>
110
- <div class="kit-press" :data-tpl="tpl" :data-layout="layout">
111
- <div v-if="loading" class="kit-state">{{ $t('kit.loading') }}</div>
112
- <div v-else-if="error || !kit" class="kit-state">
113
- <h2>{{ $t('kit.press.not_found') }}</h2>
114
- </div>
115
-
116
- <template v-else>
117
- <!-- CAPA / COVER -->
118
- <header class="kit-cover">
119
- <img v-if="kit.hero_image_url" class="kit-cover-art" :src="kit.hero_image_url" :alt="kit.name" />
120
- <div class="kit-cover-inner">
121
- <img v-if="kit.cover_image_url" class="kit-cover-poster" :src="kit.cover_image_url" :alt="kit.name" />
122
- <div class="kit-cover-text">
123
- <span v-if="kit.release_status" class="kit-eyebrow">{{ String(kit.release_status).replace(/_/g, ' ') }}</span>
124
- <img v-if="kit.logo_url" class="kit-cover-logo-img" :src="kit.logo_url" :alt="kit.name" />
125
- <h1 v-else>{{ kit.name }}</h1>
126
- <p v-if="kit.tagline" class="kit-tagline">{{ kit.tagline }}</p>
127
- <p v-if="kit.developer" class="kit-kicker">{{ kit.developer }}</p>
128
- <div class="kit-actions">
129
- <a v-if="allowDownload" class="kit-btn kit-btn-primary" @click="track('download')">{{ $t('kit.press.download_assets') }}</a>
130
- <a v-if="kit.press_contact_email" class="kit-btn kit-btn-ghost" :href="`mailto:${kit.press_contact_email}`" @click="track('contact')">{{ $t('kit.press.request_key') }}</a>
131
- </div>
132
- </div>
133
- </div>
134
- </header>
135
-
136
- <div class="kit-wrap">
137
- <div class="kit-layout">
138
- <aside class="kit-sidebar">
139
- <div class="kit-card kit-factsheet">
140
- <h3 class="kit-fact-title">{{ $t('kit.press.fact_sheet') }}</h3>
141
- <div v-for="f in facts" :key="f.label" class="kit-fact">
142
- <span class="k">{{ $te(f.label) ? $t(f.label) : f.label }}</span>
143
- <a v-if="f.href" class="v accent" :href="f.href" target="_blank" rel="noopener" @click="track('click')">{{ f.value }}</a>
144
- <span v-else class="v" :class="{ accent: f.accent }">{{ f.value }}</span>
145
- </div>
146
- <div v-if="socials.length" class="kit-fact">
147
- <span class="k">{{ $t('kit.press.social') }}</span>
148
- <div class="kit-socials">
149
- <a v-for="s in socials" :key="s.platform" :href="s.url" target="_blank" rel="noopener" @click="track('click')">{{ s.platform.charAt(0).toUpperCase() }}</a>
150
- </div>
151
- </div>
152
- </div>
153
- </aside>
154
-
155
- <main class="kit-main">
156
- <section v-if="kit.description" class="kit-block">
157
- <h2>{{ $t('kit.press.about') }}</h2>
158
- <div class="kit-lead" v-html="kit.description" />
159
- </section>
160
-
161
- <section v-if="features.length" class="kit-block">
162
- <h2>{{ $t('kit.press.features') }}</h2>
163
- <div class="kit-features">
164
- <div v-for="(f, i) in features" :key="i" class="kit-feature"><span class="m">▸</span>{{ f }}</div>
165
- </div>
166
- </section>
167
-
168
- <section v-if="videos.length" class="kit-block">
169
- <h2>{{ $t('kit.press.videos') }}</h2>
170
- <PressKitVideoPlayer :videos="videos" />
171
- </section>
172
-
173
- <section v-if="screenshots.length" class="kit-block">
174
- <div class="kit-shead"><h2>{{ $t('kit.press.screenshots') }}</h2><a v-if="allowDownload" class="kit-dl" @click="track('download')">{{ $t('kit.press.download_all') }}</a></div>
175
- <div class="kit-gallery">
176
- <a v-for="(s, i) in screenshots" :key="i" class="kit-shot" :href="s.url" target="_blank" rel="noopener"><img :src="s.thumbnail_url || s.url" :alt="s.title || ''" /></a>
177
- </div>
178
- </section>
179
-
180
- <section v-if="brandAssets.length" class="kit-block">
181
- <div class="kit-shead"><h2>{{ $t('kit.press.logo_icons') }}</h2></div>
182
- <div class="kit-logos">
183
- <a v-for="(b, i) in brandAssets" :key="i" class="kit-logo-tile" :href="b.url" target="_blank" rel="noopener" @click="track('download')">
184
- <span class="ic">◆</span>{{ b.title || b.type }}
185
- </a>
186
- </div>
187
- </section>
188
-
189
- <!-- NEW: Conteúdos & Artigos (community + external) -->
190
- <section v-if="articles.length" class="kit-block">
191
- <div class="kit-shead"><h2>{{ $t('kit.press.articles') }}</h2></div>
192
- <div class="kit-articles">
193
- <a v-for="(a, i) in articles" :key="i" class="kit-article" :href="a.url" target="_blank" rel="noopener" @click="track('click')">
194
- <div class="thumb" :style="a.image ? { backgroundImage: `url(${a.image})` } : {}"><span class="src" :class="{ ext: a.external }">{{ a.external ? $t('kit.press.external') : 'Mundo Gamer' }}</span></div>
195
- <div class="a-body"><h4>{{ a.title }}</h4><div class="meta">{{ a.source }}<span v-if="a.date"> · {{ a.date }}</span></div></div>
196
- </a>
197
- </div>
198
- </section>
199
-
200
- <!-- NEW: Cobertura de Criadores -->
201
- <section v-if="coverage.length" class="kit-block">
202
- <h2>{{ $t('kit.press.coverage') }}</h2>
203
- <div class="kit-creators">
204
- <a v-for="(c, i) in coverage" :key="i" class="kit-creator" :href="c.url || undefined" target="_blank" rel="noopener">
205
- <div class="av" :style="c.avatar ? { backgroundImage: `url(${c.avatar})` } : {}" />
206
- <div class="cn">{{ c.name }}</div>
207
- <div class="cp">{{ c.platform }}<span v-if="c.followers"> · {{ c.followers }}</span></div>
208
- <div v-if="c.label" class="cv">{{ c.label }}</div>
209
- </a>
210
- </div>
211
- </section>
212
-
213
- <section v-if="awards.length" class="kit-block">
214
- <h2>{{ $t('kit.press.awards') }}</h2>
215
- <PressKitAwards :awards="awards" />
216
- </section>
217
-
218
- <section v-if="quotes.length" class="kit-block">
219
- <h2>{{ $t('kit.press.press') }}</h2>
220
- <PressKitQuotes :quotes="quotes" />
221
- </section>
222
-
223
- <!-- Créditos -> Equipe -->
224
- <section v-if="team.length" class="kit-block">
225
- <h2>{{ $t('kit.press.team') }}</h2>
226
- <div class="kit-team">
227
- <div v-for="(m, i) in team" :key="i" class="kit-member"><span>{{ m.name }}</span><span class="role">{{ m.role }}</span></div>
228
- </div>
229
- </section>
230
-
231
- <!-- NEW: Ecossistema Mundo Gamer -->
232
- <section v-if="hasEcosystem" class="kit-block">
233
- <h2>{{ $t('kit.press.ecosystem') }}</h2>
234
- <div class="kit-eco">
235
- <div v-if="showcases.length" class="kit-eco-card full">
236
- <div class="kit-eco-h"><span class="ic">★</span>{{ $t('kit.press.showcases') }}</div>
237
- <div class="kit-sbadges">
238
- <span v-for="(s, i) in showcases" :key="i" class="kit-sbadge" :class="{ feat: s.featured }"><span class="medal">{{ s.featured ? '🏵️' : '🎮' }}</span>{{ s.name }}<span v-if="s.year"> {{ s.year }}</span></span>
239
- </div>
240
- </div>
241
- <div v-if="kit.ventures_url" class="kit-eco-card">
242
- <div class="kit-eco-h"><span class="ic">◈</span>MGN Ventures</div>
243
- <a class="kit-vrow" :href="kit.ventures_url" target="_blank" rel="noopener" @click="track('click')"><span class="vlogo">VENTURES</span><span class="vtxt">{{ $t('kit.press.ventures_text') }}</span></a>
244
- </div>
245
- <a v-if="kit.magazine_feature" class="kit-eco-card kit-mag" :href="kit.magazine_feature.url" target="_blank" rel="noopener" @click="track('click')">
246
- <div class="kit-eco-h" style="width:100%"><span class="ic">▤</span>MGN Magazine</div>
247
- <div class="kit-mag-row"><div class="mcover" :style="kit.magazine_feature.cover ? { backgroundImage: `url(${kit.magazine_feature.cover})` } : {}" /><div><div class="mtitle">{{ kit.magazine_feature.title }}</div><div class="mmeta">{{ kit.magazine_feature.issue }}</div></div></div>
248
- </a>
249
- <div v-if="kit.community_url" class="kit-eco-card full">
250
- <div class="kit-ccta">
251
- <div><div class="ctitle">{{ $t('kit.press.follow_community', { name: kit.name }) }}</div><div v-if="kit.community_followers" class="cmeta">{{ kit.community_followers }} {{ $t('kit.press.followers') }}</div></div>
252
- <a class="kit-btn kit-btn-primary" :href="kit.community_url" target="_blank" rel="noopener" @click="track('click')">{{ $t('kit.press.follow') }}</a>
253
- </div>
254
- </div>
255
- </div>
256
- </section>
257
-
258
- <section v-if="kit.press_contact_email" class="kit-block">
259
- <div class="kit-cta">
260
- <h2>{{ $t('kit.press.covering', { name: kit.name }) }}</h2>
261
- <p>{{ $t('kit.press.cta_text') }}</p>
262
- <a class="kit-btn kit-btn-primary" :href="`mailto:${kit.press_contact_email}`" @click="track('contact')">{{ $t('kit.press.request_key') }}</a>
263
- </div>
264
- </section>
265
-
266
- <p class="kit-madewith">
267
- {{ $t('kit.press.made_with') }} <strong>Mundo Gamer</strong> —
268
- <a :href="kit.create_kit_url || `${agencyBase}/presskit`" target="_blank" rel="noopener">{{ $t('kit.press.create_yours') }}</a>
269
- </p>
270
- </main>
271
- </div>
272
- </div>
273
- </template>
274
- </div>
275
- </template>