@mundogamernetwork/shared-ui 1.5.0 → 1.7.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.
package/locales/ro.json CHANGED
@@ -240,5 +240,120 @@
240
240
  "common": {
241
241
  "video_thumbnail": "Miniatură video",
242
242
  "expanded_image": "Imagine mărită"
243
+ },
244
+ "tv": {
245
+ "dashboard": {
246
+ "indie_wall": {
247
+ "about_section": "Despre mural",
248
+ "anonymous": "Anonim",
249
+ "cancel": "Anulează",
250
+ "confirm_support": "Confirmă susținerea",
251
+ "cta_create_btn": "Create my account",
252
+ "cta_create_desc": "Create an account to track your support, receive updates, and join other walls.",
253
+ "cta_create_skip": "Continue without an account",
254
+ "cta_create_title": "Want to save your support?",
255
+ "drag_to_crop": "Drag to crop",
256
+ "error_area_occupied": "This area is already taken. Choose another space on the wall.",
257
+ "error_gateway": "Could not start payment. Try again.",
258
+ "error_save": "Could not save your support. Try again.",
259
+ "filled": "Completat",
260
+ "footer_create_cta": "Create your own wall",
261
+ "footer_made_with": "Made with",
262
+ "goal_card_cta": "Susține acest obiectiv",
263
+ "goal_card_goal": "Goal",
264
+ "goal_card_missing": "mai lipsesc {count} pixeli pentru următoarea livrare",
265
+ "goal_card_missing_amount": "mai lipsesc {amount} pentru următoarea livrare",
266
+ "goal_card_result": "Result",
267
+ "goal_card_select": "Select goal",
268
+ "goal_card_you_help": "You help",
269
+ "goals": "Obiective",
270
+ "guest_email_label": "Your email",
271
+ "guest_email_placeholder": "you@email.com",
272
+ "guest_intro": "You can support without creating an account. Use a name and email to receive confirmation.",
273
+ "guest_name_label": "Public name",
274
+ "guest_name_placeholder": "How do you want to appear on the wall?",
275
+ "just_supported": "Support received",
276
+ "leaderboard_pixels": "pixels",
277
+ "leaderboard_purchases": "support",
278
+ "leaderboard_purchases_plural": "supports",
279
+ "leaderboard_show_less": "Show less",
280
+ "leaderboard_show_more": "Show more",
281
+ "leaderboard_title": "Supporter",
282
+ "leaderboard_title_span": "leaderboard",
283
+ "message": "Message",
284
+ "message_placeholder": "Leave a message for the creator",
285
+ "mural_by": "by {name}",
286
+ "mural_click_hint": "Click a pixel to see who supported",
287
+ "mural_grid": "Wall",
288
+ "mural_legend_free": "Free",
289
+ "mural_legend_taken": "Taken",
290
+ "mural_position": "Poziție",
291
+ "mural_support_now": "Support now",
292
+ "mural_title_field": "Support title",
293
+ "mural_title_placeholder": "Ex: First pixels of the project",
294
+ "not_found": "Wall not found.",
295
+ "pay_agree": "I agree to the support and billing terms.",
296
+ "pay_agree_rights": "I understand the image and message may appear publicly on the wall.",
297
+ "pay_paypal": "PayPal",
298
+ "pay_paypal_meta": "Secure payment through PayPal",
299
+ "pay_pix": "Pix",
300
+ "pay_pix_soon": "Coming soon",
301
+ "pay_stripe": "Card",
302
+ "pay_stripe_meta": "Secure payment through Stripe",
303
+ "pixels_available": "Available pixels",
304
+ "processing_close": "Close",
305
+ "processing_desc": "We are confirming your support. This may take a few seconds.",
306
+ "processing_timeout_desc": "Confirmation is taking longer than usual. You can close this and check again later.",
307
+ "processing_timeout_title": "Still processing",
308
+ "processing_title": "Processing support",
309
+ "raised": "Strâns",
310
+ "reached": "Atins",
311
+ "recent_supporters": "Recent supporters",
312
+ "share_copy_link": "Copy link",
313
+ "share_join_cta": "Susține și tu acest mural",
314
+ "share_pixel_cta": "Distribuie această susținere",
315
+ "share_pixel_default_desc": "See who is supporting the {wall} wall.",
316
+ "share_pixel_text": "{name} supported the {wall} wall. Join in and support too.",
317
+ "share_pixel_title": "{name} supported {wall}",
318
+ "share_via_facebook": "Share on Facebook",
319
+ "share_via_twitter": "Share on X",
320
+ "share_via_whatsapp": "Share on WhatsApp",
321
+ "sold": "sold",
322
+ "spots_unit": "pixeli",
323
+ "step_back": "Back",
324
+ "step_block_auto": "Choose automatically",
325
+ "step_block_confirm": "Confirm position",
326
+ "step_block_help": "Choose where your {count} pixels will appear on the wall.",
327
+ "step_block_manual": "Choose manually",
328
+ "step_block_quantity": "Quantity",
329
+ "step_block_shape": "Shape",
330
+ "step_block_title": "Choose your space",
331
+ "step_block_total": "{total} pixels",
332
+ "step_customize_continue": "Continue",
333
+ "step_customize_help": "Upload your image, title, and message to appear on the wall.",
334
+ "step_customize_image": "Support image",
335
+ "step_customize_image_hint": "Use a square image or crop it before continuing.",
336
+ "step_customize_link": "Optional link",
337
+ "step_customize_link_error": "Enter a valid link.",
338
+ "step_customize_remove_image": "Remove image",
339
+ "step_customize_replace_image": "Replace image",
340
+ "step_customize_title": "Customize your support",
341
+ "step_customize_upload_image": "Upload image",
342
+ "step_goal_help": "Choose a goal to direct your support.",
343
+ "step_goal_skip_desc": "Your support goes to the general wall without linking to a specific goal.",
344
+ "step_goal_skip_title": "Support without a goal",
345
+ "step_goal_title": "Choose a goal",
346
+ "step_package_custom_cta": "Set quantity",
347
+ "step_package_custom_desc": "Choose how many pixels you want to buy.",
348
+ "step_package_custom_title": "Custom",
349
+ "step_package_help": "Choose a pixel package for the wall.",
350
+ "step_package_title": "Choose your package",
351
+ "step_pay_method": "Payment method",
352
+ "step_pay_title": "Payment",
353
+ "step_pay_total": "Total",
354
+ "supporters": "Susținători",
355
+ "tiers": "Packages"
356
+ }
357
+ }
243
358
  }
244
359
  }
package/nuxt.config.ts CHANGED
@@ -51,7 +51,14 @@ import { dirname } from "node:path";
51
51
  const SHARED_UI_COMPONENT_PRIORITY = 1000;
52
52
 
53
53
  const require = createRequire(`${process.cwd()}/`);
54
- const packageRoot = dirname(require.resolve("@mundogamernetwork/shared-ui/package.json"));
54
+ let packageRoot: string;
55
+ try {
56
+ packageRoot = dirname(require.resolve("@mundogamernetwork/shared-ui/package.json"));
57
+ } catch {
58
+ // The package does not resolve by its own published name while running the
59
+ // local playground directly from this repository.
60
+ packageRoot = process.cwd();
61
+ }
55
62
 
56
63
  export default defineNuxtConfig({
57
64
  components: [
@@ -92,8 +99,8 @@ export default defineNuxtConfig({
92
99
  ],
93
100
  },
94
101
  css: [
95
- "@mundogamernetwork/shared-ui/assets/kit/kit-base.css",
96
- "@mundogamernetwork/shared-ui/assets/kit/kit-theme.css",
102
+ `${packageRoot}/assets/kit/kit-base.css`,
103
+ `${packageRoot}/assets/kit/kit-theme.css`,
97
104
  ],
98
105
  runtimeConfig: {
99
106
  public: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mundogamernetwork/shared-ui",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Mundo Gamer Network - Shared UI Layer (Nuxt 3)",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -123,6 +123,7 @@
123
123
  :src="card.cover"
124
124
  :alt="card.text"
125
125
  :class="{ 'gray-out': card.isExpired || card.isUpcoming || !card.user_can_access }"
126
+ :style="{ objectPosition: `${card.coverFocalX * 100}% ${card.coverFocalY * 100}%` }"
126
127
  @error="(e) => { (e.target as HTMLImageElement).src = '/imgs/no-cover-img.jpg' }"
127
128
  />
128
129
 
@@ -419,7 +420,7 @@ const sortMode = ref<"recent" | "popular">("recent")
419
420
  // ------------------------------------------------------------------
420
421
  // Data
421
422
  // ------------------------------------------------------------------
422
- const sliderImages = ref<Array<{ slug: string; cover: string; name: string; id: number }>>([])
423
+ const sliderImages = ref<Array<{ slug: string; cover: string; name: string; id: number; coverFocalX: number; coverFocalY: number }>>([])
423
424
  const cards = ref<Array<any>>([])
424
425
  const redeemedCards = ref<Array<any>>([])
425
426
 
@@ -529,11 +530,9 @@ async function loadFeaturedCampaigns() {
529
530
  .filter((item: any) => (truthyFlag(item.featured) || truthyFlag(item.highlight)) && truthyFlag(item.is_open))
530
531
  .map((item: any) => ({
531
532
  id: item.id,
532
- cover: item.game?.url_banner_src || item.cover_src,
533
- // Only applies to the cover_src fallback — the game's own
534
- // url_banner_src is already banner-shaped and needs no repositioning.
535
- coverFocalX: item.game?.url_banner_src ? 0.5 : (item.cover_focal_x ?? 0.5),
536
- coverFocalY: item.game?.url_banner_src ? 0.5 : (item.cover_focal_y ?? 0.5),
533
+ cover: item.cover_src || item.game?.url_banner_src || item.game?.url_cover_src || "/imgs/no-cover-img.jpg",
534
+ coverFocalX: item.cover_src ? (item.cover_focal_x ?? 0.5) : 0.5,
535
+ coverFocalY: item.cover_src ? (item.cover_focal_y ?? 0.5) : 0.5,
537
536
  name: item.name,
538
537
  slug: item.slug,
539
538
  }))
@@ -592,6 +591,8 @@ async function loadCampaigns(page = 1) {
592
591
  type: item.type?.name,
593
592
  typeId: item.type?.id,
594
593
  cover: item.cover_src || item.game?.url_cover_src || item.game?.url_banner_src || "/imgs/no-cover-img.jpg",
594
+ coverFocalX: item.cover_src ? (item.cover_focal_x ?? 0.5) : 0.5,
595
+ coverFocalY: item.cover_src ? (item.cover_focal_y ?? 0.5) : 0.5,
595
596
  text: item.name,
596
597
  time: item.time_to_expire ? item.time_to_expire.split(":")[0] : "0",
597
598
  slug: item.slug,
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { fetchPublicMediaKit, trackMediaKitEvent } from '../../services/mediaKitService';
3
3
  import MediaKitWallBlock from '../../components/indie-wall/MediaKitWallBlock.vue';
4
+ import MgMediaKitStore from '../../components/MediaKit/MgMediaKitStore.vue';
4
5
 
5
6
  const route = useRoute();
6
7
  const slug = computed(() => route.params.slug as string);
@@ -25,6 +26,15 @@ const template = computed(() => kit.value?.template || 'creator');
25
26
  const platforms = computed(() => kit.value?.platforms ?? []);
26
27
  const totalFollowers = computed(() => kit.value?.total_followers ?? 0);
27
28
  const isPremium = computed(() => kit.value?.is_premium ?? false);
29
+ const coverUrl = computed(() => kit.value?.cover_url || streamer.value?.cover || null);
30
+ const portfolioImages = computed(() => kit.value?.portfolio_images ?? streamer.value?.portfolio_images ?? []);
31
+ const brandLogos = computed(() => kit.value?.brand_logos ?? streamer.value?.brand_logos ?? []);
32
+ const creatorStore = computed(() => kit.value?.creator_store ?? null);
33
+ const customLinks = computed(() => kit.value?.custom_links ?? streamer.value?.custom_links ?? []);
34
+
35
+ const coverStyle = computed(() => coverUrl.value
36
+ ? { backgroundImage: `linear-gradient(180deg, rgba(5,6,10,.14), rgba(5,6,10,.92)), url("${String(coverUrl.value).replace(/"/g, '\\"')}")` }
37
+ : undefined);
28
38
 
29
39
  const PLATFORM_LABEL: Record<string, string> = {
30
40
  instagram: 'Instagram', tiktok: 'TikTok', youtube: 'YouTube', twitch: 'Twitch', x: 'X',
@@ -109,7 +119,7 @@ function onDownload() {
109
119
  <div v-else-if="error || !kit" class="kit-state"><p>{{ $t('kit.media.not_available') }}</p></div>
110
120
 
111
121
  <template v-else>
112
- <div class="mk-cover" />
122
+ <div class="mk-cover" :class="{ 'has-image': coverUrl }" :style="coverStyle" />
113
123
 
114
124
  <div class="kit-wrap mk-sections">
115
125
  <header class="mk-header">
@@ -118,6 +128,7 @@ function onDownload() {
118
128
 
119
129
  <div class="mk-hid">
120
130
  <h1>{{ streamer.name }}</h1>
131
+ <p v-if="streamer.custom_headline" class="mk-headline">{{ streamer.custom_headline }}</p>
121
132
  <div class="mk-badges">
122
133
  <span v-if="streamer.platform" class="mk-badge plat">{{ streamer.platform }}</span>
123
134
  <span v-if="streamer.tier && streamer.tier !== 'free'" class="mk-badge tier">{{ String(streamer.tier).toUpperCase() }}</span>
@@ -132,6 +143,25 @@ function onDownload() {
132
143
  </div>
133
144
  </header>
134
145
 
146
+ <!-- Personal link hub: replaces a separate link-in-bio service. -->
147
+ <nav class="mk-link-hub no-print" data-sec="links" :aria-label="$t('kit.media.quick_links')">
148
+ <a v-for="(link, index) in customLinks" :key="`${link.url}-${index}`" class="mk-link" :class="{ 'mk-link--primary': index === 0 }" :href="link.url" target="_blank" rel="noopener" @click="track('click')">
149
+ <span>{{ link.label }}</span><b>↗</b>
150
+ </a>
151
+ <a v-if="streamer.contact_email" class="mk-link mk-link--primary" :href="`mailto:${streamer.contact_email}`" @click="track('contact')">
152
+ <span>{{ $t('kit.media.work_with_me') }}</span><b>↗</b>
153
+ </a>
154
+ <a v-if="creatorStore" class="mk-link" href="#store" @click="track('click')">
155
+ <span>{{ $t('kit.media.visit_store') }}</span><b>↓</b>
156
+ </a>
157
+ <a v-if="userId" class="mk-link" href="#support" @click="track('click')">
158
+ <span>{{ $t('kit.media.visit_mural') }}</span><b>↓</b>
159
+ </a>
160
+ <a v-for="s in socials" :key="s.platform" class="mk-link mk-link--social" :href="s.url" target="_blank" rel="noopener" @click="track('click')">
161
+ <span>{{ PLATFORM_LABEL[s.platform] || s.platform }}</span><b>↗</b>
162
+ </a>
163
+ </nav>
164
+
135
165
  <!-- Reach / multi-platform followers -->
136
166
  <section v-if="platforms.length" class="kit-block" data-sec="reach">
137
167
  <h2>{{ $t('kit.media.reach') }}</h2>
@@ -151,6 +181,24 @@ function onDownload() {
151
181
  </div>
152
182
  </section>
153
183
 
184
+ <!-- Mundo Gamer creator store -->
185
+ <section v-if="creatorStore" id="store" class="kit-block kit-card mk-store-block" data-sec="store">
186
+ <MgMediaKitStore :store="creatorStore" :contact-email="streamer.contact_email" @click="track('click')" />
187
+ </section>
188
+
189
+ <!-- Creator-selected portfolio and brand imagery -->
190
+ <section v-if="portfolioImages.length || brandLogos.length" class="kit-block" data-sec="portfolio">
191
+ <h2>{{ $t('kit.media.portfolio') }}</h2>
192
+ <div v-if="portfolioImages.length" class="mk-portfolio">
193
+ <a v-for="(image, index) in portfolioImages" :key="image" :href="image" target="_blank" rel="noopener" @click="track('click')">
194
+ <img :src="image" :alt="`${streamer.name} — ${index + 1}`" />
195
+ </a>
196
+ </div>
197
+ <div v-if="brandLogos.length" class="mk-brand-logos">
198
+ <img v-for="logo in brandLogos" :key="logo" :src="logo" :alt="$t('kit.media.partner_brand')" />
199
+ </div>
200
+ </section>
201
+
154
202
  <!-- TV streaming metrics -->
155
203
  <section v-if="stats" class="kit-block" data-sec="metrics">
156
204
  <h2>{{ $t('kit.media.key_metrics') }}</h2>
@@ -262,18 +310,8 @@ function onDownload() {
262
310
  </div>
263
311
  </section>
264
312
 
265
- <!-- Social links -->
266
- <section v-if="socials.length" class="kit-block no-print" data-sec="connect">
267
- <h2>{{ $t('kit.media.connect') }}</h2>
268
- <div class="mk-socials">
269
- <a v-for="s in socials" :key="s.platform" class="mk-soc" :href="s.url" target="_blank" rel="noopener" @click="track('click')">
270
- {{ s.platform }}
271
- </a>
272
- </div>
273
- </section>
274
-
275
313
  <!-- IndieWall / mural — self-contained widget (own fetch, realtime, stepper) -->
276
- <section v-if="userId" class="kit-block" data-sec="support">
314
+ <section v-if="userId" id="support" class="kit-block" data-sec="support">
277
315
  <MediaKitWallBlock :user-id="userId" />
278
316
  </section>
279
317
 
@@ -308,8 +346,10 @@ function onDownload() {
308
346
 
309
347
  /* Cover */
310
348
  .mk-cover {
311
- height: 200px;
349
+ height: 280px;
312
350
  background: linear-gradient(120deg, var(--kit-accent-2, #6b15ac) 0%, #2a0e4e 50%, var(--kit-bg) 100%);
351
+ background-position: center;
352
+ background-size: cover;
313
353
  position: relative;
314
354
  border-bottom: 1px solid var(--kit-line);
315
355
 
@@ -319,44 +359,59 @@ function onDownload() {
319
359
  inset: 0;
320
360
  background: radial-gradient(circle at 80% 20%, rgba(168, 85, 247, .35), transparent 60%);
321
361
  }
362
+
363
+ &.has-image::after {
364
+ background: linear-gradient(180deg, rgba(5, 6, 10, .04), rgba(5, 6, 10, .52));
365
+ }
322
366
  }
323
367
 
324
368
  /* Header */
325
369
  .mk-header {
326
- margin-top: -70px;
370
+ margin: -82px auto 0;
327
371
  position: relative;
328
372
  z-index: 2;
329
373
  display: flex;
330
- gap: 24px;
331
- align-items: flex-end;
332
- flex-wrap: wrap;
374
+ flex-direction: column;
375
+ align-items: center;
376
+ max-width: 760px;
377
+ text-align: center;
333
378
  }
334
379
  .mk-avatar {
335
- width: 140px;
336
- height: 140px;
380
+ width: 156px;
381
+ height: 156px;
337
382
  border: 3px solid var(--kit-accent);
338
383
  object-fit: cover;
339
384
  flex-shrink: 0;
340
385
  background: var(--kit-surface-2);
386
+ box-shadow: 0 20px 52px rgba(0, 0, 0, .42);
341
387
  }
342
388
  .mk-avatar-ph {
343
389
  background: linear-gradient(135deg, var(--kit-accent-2, #6b15ac), var(--kit-accent));
344
390
  }
345
391
  .mk-hid {
346
- flex: 1;
347
- min-width: 260px;
348
- padding-bottom: 6px;
392
+ width: 100%;
393
+ padding-top: 18px;
349
394
 
350
395
  h1 {
351
396
  font-family: 'Rajdhani', sans-serif;
352
- font-size: 40px;
397
+ font-size: clamp(36px, 7vw, 54px);
353
398
  line-height: 1.05;
399
+ margin: 0;
354
400
  }
355
401
  }
402
+ .mk-headline {
403
+ margin: 8px auto 0;
404
+ max-width: 620px;
405
+ color: var(--kit-accent);
406
+ font-family: 'Rajdhani', sans-serif;
407
+ font-size: 20px;
408
+ font-weight: 600;
409
+ }
356
410
  .mk-badges {
357
411
  display: flex;
412
+ justify-content: center;
358
413
  gap: 8px;
359
- margin: 8px 0;
414
+ margin: 12px 0;
360
415
  flex-wrap: wrap;
361
416
  }
362
417
  .mk-badge {
@@ -374,13 +429,60 @@ function onDownload() {
374
429
  .mk-bio {
375
430
  color: #c4c4d2;
376
431
  font-size: 15px;
377
- max-width: 60ch;
378
- margin-top: 6px;
432
+ line-height: 1.65;
433
+ max-width: 66ch;
434
+ margin: 6px auto 0;
379
435
  }
380
436
  .mk-actions {
381
437
  display: flex;
438
+ justify-content: center;
439
+ flex-wrap: wrap;
382
440
  gap: 10px;
383
- padding-bottom: 8px;
441
+ padding-top: 18px;
442
+ }
443
+
444
+ /* Link-in-bio hub */
445
+ .mk-link-hub {
446
+ display: grid;
447
+ grid-template-columns: 1fr 1fr;
448
+ gap: 10px;
449
+ width: min(100%, 720px);
450
+ margin: 30px auto 2px;
451
+ }
452
+ .mk-link {
453
+ display: flex;
454
+ align-items: center;
455
+ justify-content: space-between;
456
+ gap: 16px;
457
+ min-height: 54px;
458
+ padding: 0 18px;
459
+ border: 1px solid var(--kit-line);
460
+ background: color-mix(in srgb, var(--kit-surface) 92%, transparent);
461
+ color: var(--kit-text);
462
+ font-size: 14px;
463
+ font-weight: 700;
464
+ text-decoration: none;
465
+ transition: transform .16s ease, border-color .16s ease, background .16s ease;
466
+
467
+ &:hover {
468
+ transform: translateY(-2px);
469
+ border-color: var(--kit-accent);
470
+ background: var(--kit-surface-2);
471
+ }
472
+
473
+ b { color: var(--kit-accent); font-size: 16px; }
474
+
475
+ &--primary {
476
+ grid-column: 1 / -1;
477
+ background: var(--kit-accent);
478
+ border-color: var(--kit-accent);
479
+ color: var(--kit-accent-ink);
480
+
481
+ b { color: var(--kit-accent-ink); }
482
+ &:hover { background: var(--kit-accent); filter: brightness(1.06); }
483
+ }
484
+
485
+ &--social { min-height: 46px; color: var(--kit-muted); }
384
486
  }
385
487
 
386
488
  /* Buttons */
@@ -434,6 +536,29 @@ function onDownload() {
434
536
  }
435
537
  }
436
538
  }
539
+ .mk-store-block { padding: 24px; scroll-margin-top: 24px; }
540
+ .mk-portfolio {
541
+ display: grid;
542
+ grid-template-columns: repeat(3, minmax(0, 1fr));
543
+ gap: 10px;
544
+
545
+ a { display: block; overflow: hidden; aspect-ratio: 4 / 3; border: 1px solid var(--kit-line); background: var(--kit-surface); }
546
+ img { width: 100%; height: 100%; object-fit: cover; transition: transform .2s ease; }
547
+ a:hover img { transform: scale(1.03); }
548
+ }
549
+ .mk-brand-logos {
550
+ display: flex;
551
+ align-items: center;
552
+ justify-content: center;
553
+ flex-wrap: wrap;
554
+ gap: 16px;
555
+ margin-top: 16px;
556
+ padding: 18px;
557
+ border: 1px solid var(--kit-line);
558
+ background: var(--kit-surface);
559
+
560
+ img { width: 112px; height: 54px; object-fit: contain; filter: grayscale(1); opacity: .8; }
561
+ }
437
562
  .mk-period {
438
563
  color: var(--kit-muted);
439
564
  font-size: 13px;
@@ -768,16 +893,19 @@ function onDownload() {
768
893
  .mk-sections { display: flex; flex-direction: column; }
769
894
  .mk-sections > .mk-header { order: 0; }
770
895
  .mk-sections > .mk-footer { order: 100; }
771
- .mk-sections > [data-sec="reach"] { order: 1; }
772
- .mk-sections > [data-sec="metrics"] { order: 2; }
773
- .mk-sections > [data-sec="audience"] { order: 3; }
774
- .mk-sections > [data-sec="styles"] { order: 4; }
775
- .mk-sections > [data-sec="games"] { order: 5; }
776
- .mk-sections > [data-sec="rates"] { order: 6; }
777
- .mk-sections > [data-sec="brands"] { order: 7; }
778
- .mk-sections > [data-sec="schedule"] { order: 8; }
779
- .mk-sections > [data-sec="connect"] { order: 9; }
780
- .mk-sections > [data-sec="support"] { order: 10; }
896
+ .mk-sections > [data-sec="links"] { order: 1; }
897
+ .mk-sections > [data-sec="reach"] { order: 2; }
898
+ .mk-sections > [data-sec="store"] { order: 3; }
899
+ .mk-sections > [data-sec="portfolio"] { order: 4; }
900
+ .mk-sections > [data-sec="metrics"] { order: 5; }
901
+ .mk-sections > [data-sec="audience"] { order: 6; }
902
+ .mk-sections > [data-sec="styles"] { order: 7; }
903
+ .mk-sections > [data-sec="games"] { order: 8; }
904
+ .mk-sections > [data-sec="rates"] { order: 9; }
905
+ .mk-sections > [data-sec="brands"] { order: 10; }
906
+ .mk-sections > [data-sec="schedule"] { order: 11; }
907
+ .mk-sections > [data-sec="connect"] { order: 12; }
908
+ .mk-sections > [data-sec="support"] { order: 13; scroll-margin-top: 24px; }
781
909
 
782
910
  [data-template="performance"] {
783
911
  .mk-sections > [data-sec="metrics"] { order: 1; }
@@ -803,7 +931,20 @@ function onDownload() {
803
931
  .mk-cols2, .mk-rates, .mk-bundles { grid-template-columns: 1fr; }
804
932
  .mk-brands { grid-template-columns: repeat(3, 1fr); }
805
933
  .mk-schedule { grid-template-columns: repeat(4, 1fr); }
806
- .mk-header { flex-direction: column; align-items: flex-start; }
934
+ .mk-portfolio { grid-template-columns: 1fr 1fr; }
935
+ }
936
+
937
+ @media (max-width: 560px) {
938
+ .kit-wrap { padding-inline: 14px; }
939
+ .mk-cover { height: 230px; }
940
+ .mk-avatar { width: 132px; height: 132px; }
941
+ .mk-header { margin-top: -68px; }
942
+ .mk-link-hub { grid-template-columns: 1fr; }
943
+ .mk-link--primary { grid-column: auto; }
944
+ .mk-actions { width: 100%; }
945
+ .kit-btn { flex: 1; justify-content: center; }
946
+ .mk-portfolio { grid-template-columns: 1fr; }
947
+ .mk-store-block { padding: 16px; }
807
948
  }
808
949
 
809
950
  @media print {
@@ -811,7 +952,7 @@ function onDownload() {
811
952
  }
812
953
 
813
954
  // ── Scroll unlock (body has overflow-y: hidden globally in some frontends) ───
814
- :global(body):has(.pk-page) {
955
+ :global(body):has(.kit-media) {
815
956
  overflow-y: auto !important;
816
957
  max-height: none !important;
817
958
  }
@@ -281,7 +281,22 @@ async function onStepperSuccess() {
281
281
 
282
282
  function supporterDisplayName(s: any): string {
283
283
  if (s.is_anonymous) return t('tv.dashboard.indie_wall.anonymous')
284
- return s.title || s.user?.name || t('tv.dashboard.indie_wall.anonymous')
284
+ return s.user?.nickname
285
+ || s.user?.username
286
+ || s.user?.full_name
287
+ || s.user?.name
288
+ || s.guest_name
289
+ || t('tv.dashboard.indie_wall.anonymous')
290
+ }
291
+
292
+ function supporterAvatarUrl(s: any): string {
293
+ if (s.is_anonymous) return ''
294
+ return s.user?.avatar_url || s.user?.profile_image || s.user?.avatar || s.user?.image || ''
295
+ }
296
+
297
+ function supporterTitle(s: any): string {
298
+ if (!s?.title) return ''
299
+ return s.title !== supporterDisplayName(s) ? s.title : ''
285
300
  }
286
301
 
287
302
  const requestUrl = useRequestURL()
@@ -601,11 +616,11 @@ onUnmounted(() => {
601
616
  </div>
602
617
  <div class="supp-body">
603
618
  <div class="supp-identity">
604
- <img v-if="s.user?.avatar_url || s.user?.profile_image" :src="s.user.avatar_url || s.user.profile_image" class="supp-avi" alt="" />
619
+ <img v-if="supporterAvatarUrl(s)" :src="supporterAvatarUrl(s)" class="supp-avi" alt="" />
605
620
  <div v-else class="supp-avi-init">{{ (supporterDisplayName(s)[0] || '?').toUpperCase() }}</div>
606
621
  <div class="supp-user-info">
607
622
  <span class="supp-name">{{ supporterDisplayName(s) }}</span>
608
- <span v-if="!s.is_anonymous && !s.user && s.guest_email" class="supp-email">{{ s.guest_email }}</span>
623
+ <span v-if="supporterTitle(s)" class="supp-title">{{ supporterTitle(s) }}</span>
609
624
  </div>
610
625
  <span class="supp-amount">{{ formatMoney(Number(s.amount_paid || 0), wall.currency) }}</span>
611
626
  </div>
@@ -721,6 +736,7 @@ onUnmounted(() => {
721
736
  </div>
722
737
  <div class="detail-body">
723
738
  <p class="detail-name">{{ supporterDisplayName(detailPixel) }}</p>
739
+ <p v-if="supporterTitle(detailPixel)" class="detail-support-title">{{ supporterTitle(detailPixel) }}</p>
724
740
  <p v-if="detailPixel.supporter_message" class="detail-msg">"{{ detailPixel.supporter_message }}"</p>
725
741
  <div class="detail-amount-row">
726
742
  <span class="detail-amount-label">{{ $t('tv.dashboard.indie_wall.spots_unit') }}</span>
@@ -1006,7 +1022,7 @@ onUnmounted(() => {
1006
1022
  color: var(--title-fg, #fff); font-size: 0.78rem; font-weight: 500;
1007
1023
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1008
1024
  }
1009
- .supp-email {
1025
+ .supp-title {
1010
1026
  color: var(--secondary-info-fg, #666); font-size: 0.65rem;
1011
1027
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1012
1028
  }
@@ -1051,7 +1067,11 @@ onUnmounted(() => {
1051
1067
  }
1052
1068
  .detail-body { padding: 20px; }
1053
1069
  .detail-name {
1054
- font-size: 1.1rem; font-weight: 700; color: var(--title-fg, #fff); margin: 0 0 8px;
1070
+ font-size: 1.1rem; font-weight: 700; color: var(--title-fg, #fff); margin: 0 0 4px;
1071
+ }
1072
+ .detail-support-title {
1073
+ color: var(--secondary-info-fg, #aaa); font-size: 0.78rem;
1074
+ line-height: 1.35; margin: 0 0 10px;
1055
1075
  }
1056
1076
  .detail-msg {
1057
1077
  color: var(--secondary-info-fg, #aaa); font-size: 0.875rem; font-style: italic;