@mundogamernetwork/shared-ui 1.8.3 → 1.8.5
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/components/indie-wall/MediaKitWallBlock.vue +7 -1
- package/components/keys/KeyCampaignDashboardCard.vue +14 -0
- package/components/keys/KeyCampaignsCarousel.vue +13 -95
- package/components/ui/MgHeaderUIUser.vue +1 -1
- package/components/ui/MgWalletPlanBadge.vue +1 -1
- package/package.json +1 -1
- package/pages/key-campaign/[slug].vue +8 -2
- package/pages/key-campaigns/[slug].vue +16 -4
- package/pages/key-campaigns/index.vue +80 -8
- package/pages/media-kit/[slug].vue +667 -611
- package/pages/presskit/game/[slug].vue +8 -2
- package/public/imgs/default-avatar.png +0 -0
- package/services/mediaKitProposalService.ts +35 -0
- package/stores/shortlist.ts +113 -0
|
@@ -4,6 +4,7 @@ import MuralCanvas from './MuralCanvas.vue'
|
|
|
4
4
|
import SupportStepper from './SupportStepper.vue'
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{ userId: number | string }>()
|
|
7
|
+
const emit = defineEmits<{ (e: 'wall-status', hasWall: boolean): void }>()
|
|
7
8
|
const localePath = useLocalePath()
|
|
8
9
|
const authStore = useAuthStore()
|
|
9
10
|
const { locale } = useI18n()
|
|
@@ -102,8 +103,12 @@ async function load() {
|
|
|
102
103
|
const res = await fetchStreamerActiveWalls(props.userId)
|
|
103
104
|
const list = res.data?.data ?? res.data ?? []
|
|
104
105
|
const first = Array.isArray(list) ? list.find((w: any) => w.show_on_media_kit) ?? list[0] : null
|
|
105
|
-
if (!first)
|
|
106
|
+
if (!first) {
|
|
107
|
+
emit('wall-status', false)
|
|
108
|
+
return
|
|
109
|
+
}
|
|
106
110
|
wall.value = first
|
|
111
|
+
emit('wall-status', true)
|
|
107
112
|
const [goalsRes, supRes] = await Promise.all([
|
|
108
113
|
fetchPublicWallGoals(first.slug || first.id).catch(() => ({ data: { data: [] } })),
|
|
109
114
|
getWallSupporters(first.slug || first.id, { per_page: 500 }),
|
|
@@ -112,6 +117,7 @@ async function load() {
|
|
|
112
117
|
supporters.value = (supRes.data?.data ?? []).filter((p: any) => p.status)
|
|
113
118
|
} catch {
|
|
114
119
|
// no wall — renders nothing
|
|
120
|
+
emit('wall-status', false)
|
|
115
121
|
} finally {
|
|
116
122
|
loading.value = false
|
|
117
123
|
}
|
|
@@ -7,6 +7,8 @@ const props = defineProps<{
|
|
|
7
7
|
slug: string;
|
|
8
8
|
name: string;
|
|
9
9
|
cover: string | null;
|
|
10
|
+
cover_focal_x?: number | null;
|
|
11
|
+
cover_focal_y?: number | null;
|
|
10
12
|
game_cover?: string | null;
|
|
11
13
|
available_count: number;
|
|
12
14
|
is_exclusive?: boolean;
|
|
@@ -34,6 +36,17 @@ const cardCover = computed(() => {
|
|
|
34
36
|
return c.cover || c.cover_src || c.game?.url_cover_src || c.game_cover || "/imgs/no-cover-img.jpg";
|
|
35
37
|
});
|
|
36
38
|
|
|
39
|
+
// Only apply the campaign's own focal point to its own cover — a game-cover/no-cover
|
|
40
|
+
// fallback wasn't framed against that point and would crop wrong if we reused it.
|
|
41
|
+
const cardCoverPosition = computed(() => {
|
|
42
|
+
const c = props.card as any;
|
|
43
|
+
const hasOwnCover = Boolean(c.cover || c.cover_src);
|
|
44
|
+
if (!hasOwnCover) return "50% 50%";
|
|
45
|
+
const fx = c.cover_focal_x ?? 0.5;
|
|
46
|
+
const fy = c.cover_focal_y ?? 0.5;
|
|
47
|
+
return `${fx * 100}% ${fy * 100}%`;
|
|
48
|
+
});
|
|
49
|
+
|
|
37
50
|
const userCanAccess = computed(() => props.publicMode || props.card.user_can_access !== false);
|
|
38
51
|
const isExpired = computed(() => props.card.isExpired ?? false);
|
|
39
52
|
const isUpcoming = computed(() => props.card.isUpcoming ?? false);
|
|
@@ -99,6 +112,7 @@ const detailRoute = computed(() =>
|
|
|
99
112
|
<img
|
|
100
113
|
:src="cardCover"
|
|
101
114
|
:alt="card.name"
|
|
115
|
+
:style="{ objectPosition: cardCoverPosition }"
|
|
102
116
|
:class="{ 'gray-out': isExpired || isUpcoming || !userCanAccess }"
|
|
103
117
|
@error="(e: Event) => { (e.target as HTMLImageElement).src = '/imgs/no-cover-img.jpg' }"
|
|
104
118
|
/>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { ref, onMounted, inject } from "vue"
|
|
3
3
|
import { fetchCampaigns } from "../../services/campaignService"
|
|
4
|
+
import KeyCampaignDashboardCard from "./KeyCampaignDashboardCard.vue"
|
|
4
5
|
|
|
5
6
|
const props = defineProps<{
|
|
6
7
|
excludeId?: number | null
|
|
7
8
|
mgPlatform?: string
|
|
9
|
+
publicMode?: boolean
|
|
10
|
+
baseRoute?: string
|
|
8
11
|
}>()
|
|
9
12
|
|
|
10
13
|
const mgPlatform = props.mgPlatform || (inject<string>("mgPlatform", "MGTV"))
|
|
@@ -46,31 +49,13 @@ onMounted(async () => {
|
|
|
46
49
|
</div>
|
|
47
50
|
|
|
48
51
|
<div v-else class="kc-carousel__track">
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<img
|
|
57
|
-
v-if="item.cover_src || item.cover"
|
|
58
|
-
:src="item.cover_src || item.cover"
|
|
59
|
-
:alt="item.name"
|
|
60
|
-
/>
|
|
61
|
-
<div v-else class="kc-carousel__thumb-placeholder">
|
|
62
|
-
<i class="mg-icon mg-icon-game-key" />
|
|
63
|
-
</div>
|
|
64
|
-
<span v-if="!item.is_open" class="kc-carousel__closed">{{ $t("keys.campaigns.closed") }}</span>
|
|
65
|
-
</div>
|
|
66
|
-
<div class="kc-carousel__info">
|
|
67
|
-
<div class="kc-carousel__name">{{ item.name }}</div>
|
|
68
|
-
<div class="kc-carousel__avail">
|
|
69
|
-
{{ item.available_count ?? 0 }}/{{ item.total_count ?? item.quantity ?? "?" }}
|
|
70
|
-
{{ $t("keys.campaigns.available") }}
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
</NuxtLink>
|
|
52
|
+
<div v-for="item in items" :key="item.id" class="kc-carousel__card">
|
|
53
|
+
<KeyCampaignDashboardCard
|
|
54
|
+
:card="{ ...item, isExpired: item.is_open === false }"
|
|
55
|
+
:public-mode="publicMode"
|
|
56
|
+
:base-route="baseRoute ?? 'key-campaigns'"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
74
59
|
</div>
|
|
75
60
|
</div>
|
|
76
61
|
</template>
|
|
@@ -105,81 +90,14 @@ onMounted(async () => {
|
|
|
105
90
|
}
|
|
106
91
|
|
|
107
92
|
&__skeleton {
|
|
108
|
-
flex: 0 0
|
|
109
|
-
height:
|
|
93
|
+
flex: 0 0 200px;
|
|
94
|
+
height: 280px;
|
|
110
95
|
background: var(--bg-app-badge);
|
|
111
96
|
animation: pulse 1.4s ease-in-out infinite;
|
|
112
97
|
}
|
|
113
98
|
|
|
114
99
|
&__card {
|
|
115
|
-
flex: 0 0
|
|
116
|
-
display: flex;
|
|
117
|
-
flex-direction: column;
|
|
118
|
-
text-decoration: none;
|
|
119
|
-
color: inherit;
|
|
120
|
-
border: 1px solid var(--button-secondary-default-bg);
|
|
121
|
-
transition: border-color 0.15s;
|
|
122
|
-
|
|
123
|
-
&:hover { border-color: var(--key-accent, var(--primary, #D297FF)); }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
&__thumb {
|
|
127
|
-
position: relative;
|
|
128
|
-
width: 100%;
|
|
129
|
-
height: 101px;
|
|
130
|
-
overflow: hidden;
|
|
131
|
-
background: var(--bg-app-badge);
|
|
132
|
-
|
|
133
|
-
img {
|
|
134
|
-
width: 100%;
|
|
135
|
-
height: 100%;
|
|
136
|
-
object-fit: cover;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
&__thumb-placeholder {
|
|
141
|
-
width: 100%;
|
|
142
|
-
height: 100%;
|
|
143
|
-
display: flex;
|
|
144
|
-
align-items: center;
|
|
145
|
-
justify-content: center;
|
|
146
|
-
color: var(--secondary-info-fg);
|
|
147
|
-
font-size: 24px;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
&__closed {
|
|
151
|
-
position: absolute;
|
|
152
|
-
inset: 0;
|
|
153
|
-
display: flex;
|
|
154
|
-
align-items: center;
|
|
155
|
-
justify-content: center;
|
|
156
|
-
background: var(--overlay-dark, rgba(0, 0, 0, 0.55));
|
|
157
|
-
font-size: 11px;
|
|
158
|
-
font-weight: 700;
|
|
159
|
-
color: var(--card-article-title, #fff);
|
|
160
|
-
letter-spacing: 0.5px;
|
|
161
|
-
text-transform: uppercase;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
&__info {
|
|
165
|
-
padding: 8px 10px;
|
|
166
|
-
display: flex;
|
|
167
|
-
flex-direction: column;
|
|
168
|
-
gap: 4px;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
&__name {
|
|
172
|
-
font-size: 12px;
|
|
173
|
-
font-weight: 600;
|
|
174
|
-
color: var(--card-article-title);
|
|
175
|
-
overflow: hidden;
|
|
176
|
-
text-overflow: ellipsis;
|
|
177
|
-
white-space: nowrap;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
&__avail {
|
|
181
|
-
font-size: 11px;
|
|
182
|
-
color: var(--secondary-info-fg);
|
|
100
|
+
flex: 0 0 200px;
|
|
183
101
|
}
|
|
184
102
|
}
|
|
185
103
|
|
|
@@ -107,7 +107,7 @@ const handleLogout = async () => {
|
|
|
107
107
|
<div v-if="features.wallet && user?.coins !== null" class="row info-row">
|
|
108
108
|
<span class="coins">
|
|
109
109
|
<MGIcon icon="coin" />
|
|
110
|
-
{{ user?.coins ?? 0 }}
|
|
110
|
+
{{ Math.floor(user?.coins ?? 0).toLocaleString() }}
|
|
111
111
|
</span>
|
|
112
112
|
</div>
|
|
113
113
|
|
|
@@ -44,7 +44,7 @@ const isSubscriber = computed(() => !!(user.value as any)?.subscription?.is_subs
|
|
|
44
44
|
<div class="mg-wallet-plan">
|
|
45
45
|
<span v-if="showWallet" class="mg-wallet-plan__coins">
|
|
46
46
|
<MGIcon icon="coin" />
|
|
47
|
-
{{ walletBalance.toLocaleString() }}
|
|
47
|
+
{{ Math.floor(walletBalance).toLocaleString() }}
|
|
48
48
|
<span class="mg-wallet-plan__currency">MGC</span>
|
|
49
49
|
</span>
|
|
50
50
|
<span
|
package/package.json
CHANGED
|
@@ -78,7 +78,13 @@ const restriction = computed(() => {
|
|
|
78
78
|
|
|
79
79
|
<template v-else>
|
|
80
80
|
<div class="kc__hero">
|
|
81
|
-
<img
|
|
81
|
+
<img
|
|
82
|
+
v-if="key.cover_src"
|
|
83
|
+
:src="key.cover_src"
|
|
84
|
+
:alt="key.name"
|
|
85
|
+
class="kc__cover"
|
|
86
|
+
:style="{ objectPosition: `${(key.cover_focal_x ?? 0.5) * 100}% ${(key.cover_focal_y ?? 0.5) * 100}%` }"
|
|
87
|
+
>
|
|
82
88
|
<div class="kc__hero-body">
|
|
83
89
|
<h1 class="kc__title">{{ key.name }}</h1>
|
|
84
90
|
<div v-if="restriction" class="kc__badge">🔒 {{ restriction }}</div>
|
|
@@ -96,7 +102,7 @@ const restriction = computed(() => {
|
|
|
96
102
|
/>
|
|
97
103
|
</div>
|
|
98
104
|
|
|
99
|
-
<KeyCampaignsCarousel :exclude-id="key.id ?? null" />
|
|
105
|
+
<KeyCampaignsCarousel :exclude-id="key.id ?? null" :public-mode="true" base-route="key-campaign" />
|
|
100
106
|
</template>
|
|
101
107
|
</div>
|
|
102
108
|
</template>
|
|
@@ -840,9 +840,9 @@ onMounted(async () => {
|
|
|
840
840
|
<img
|
|
841
841
|
v-for="(u, index) in redeemedUsers?.users?.slice(0, 4)"
|
|
842
842
|
:key="index"
|
|
843
|
-
:src="u.avatar_url || '/imgs/avatar
|
|
843
|
+
:src="u.avatar_url || '/imgs/default-avatar.png'"
|
|
844
844
|
:alt="u.nickname"
|
|
845
|
-
@error="(e: Event) => { const t = e.target as HTMLImageElement; if (t
|
|
845
|
+
@error="(e: Event) => { const t = e.target as HTMLImageElement; if (t) { t.onerror = null; t.src = '/imgs/default-avatar.png' } }"
|
|
846
846
|
/>
|
|
847
847
|
</div>
|
|
848
848
|
<div class="text">
|
|
@@ -872,9 +872,9 @@ onMounted(async () => {
|
|
|
872
872
|
</div>
|
|
873
873
|
<div v-for="(u, index) in redeemedUsers?.users" :key="index" class="users-modal-row">
|
|
874
874
|
<img
|
|
875
|
-
:src="u.avatar_url || '/imgs/avatar
|
|
875
|
+
:src="u.avatar_url || '/imgs/default-avatar.png'"
|
|
876
876
|
:alt="u.nickname"
|
|
877
|
-
@error="(e: Event) => { const t = e.target as HTMLImageElement; if (t
|
|
877
|
+
@error="(e: Event) => { const t = e.target as HTMLImageElement; if (t) { t.onerror = null; t.src = '/imgs/default-avatar.png' } }"
|
|
878
878
|
/>
|
|
879
879
|
<span>{{ u.nickname || `${u.first_name || ''} ${u.last_name || ''}`.trim() }}</span>
|
|
880
880
|
</div>
|
|
@@ -926,6 +926,8 @@ onMounted(async () => {
|
|
|
926
926
|
<KeyCampaignsCarousel
|
|
927
927
|
:exclude-id="campaign?.id ?? null"
|
|
928
928
|
:mg-platform="mgPlatform"
|
|
929
|
+
:public-mode="true"
|
|
930
|
+
base-route="key-campaigns"
|
|
929
931
|
class="container"
|
|
930
932
|
/>
|
|
931
933
|
</div>
|
|
@@ -1482,6 +1484,16 @@ onMounted(async () => {
|
|
|
1482
1484
|
.time { gap: 6px; .card-time { height: auto; padding: 8px; font-size: 24px; } }
|
|
1483
1485
|
.actions .data { flex-direction: column; }
|
|
1484
1486
|
.status { flex-direction: column; align-items: start; gap: 8px; }
|
|
1487
|
+
.requirements .tags {
|
|
1488
|
+
flex-wrap: wrap;
|
|
1489
|
+
.tag {
|
|
1490
|
+
height: auto;
|
|
1491
|
+
min-height: 32px;
|
|
1492
|
+
padding: 8px;
|
|
1493
|
+
flex: 1 1 calc(50% - 6px);
|
|
1494
|
+
text-align: center;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1485
1497
|
}
|
|
1486
1498
|
}
|
|
1487
1499
|
}
|
|
@@ -196,6 +196,12 @@
|
|
|
196
196
|
</div>
|
|
197
197
|
|
|
198
198
|
<div class="text">
|
|
199
|
+
<div class="card-name">{{ card.gameName || card.text }}</div>
|
|
200
|
+
<div v-if="card.studioName || card.regions.length" class="card-studio-row">
|
|
201
|
+
<span v-if="card.studioName" class="card-studio-name">{{ card.studioName }}</span>
|
|
202
|
+
<span v-if="card.studioName && card.regions.length" class="card-studio-sep">·</span>
|
|
203
|
+
<span v-if="card.regions.length">{{ card.regions.slice(0, 2).join(", ") }}</span>
|
|
204
|
+
</div>
|
|
199
205
|
<div class="card-meta-row">
|
|
200
206
|
<div class="days" v-if="card.time > 0 && card.user_can_access">
|
|
201
207
|
<span>{{ card.time }}</span> {{ $t("keys.campaigns.days") }}
|
|
@@ -208,11 +214,17 @@
|
|
|
208
214
|
</span>
|
|
209
215
|
</div>
|
|
210
216
|
<div v-if="card.platforms && card.platforms.length" class="card-platforms">
|
|
211
|
-
<span v-for="p in card.platforms" :key="p" class="card-platform-tag">
|
|
217
|
+
<span v-for="p in card.platforms.slice(0, 4)" :key="p" class="card-platform-tag">
|
|
212
218
|
{{ platformAbbr(p) }}
|
|
213
219
|
</span>
|
|
220
|
+
<span v-if="card.platforms.length > 4" class="card-platform-tag card-platform-more">
|
|
221
|
+
+{{ card.platforms.length - 4 }}
|
|
222
|
+
</span>
|
|
214
223
|
</div>
|
|
215
|
-
|
|
224
|
+
<div v-if="card.available_count > 0 && card.user_can_access && !card.isExpired && !card.isUpcoming" class="card-availability">
|
|
225
|
+
{{ $t("keys.campaigns.keys_left", { n: card.available_count }) }}
|
|
226
|
+
</div>
|
|
227
|
+
<div v-if="card.gameName && card.gameName !== card.text" class="card-campaign-name">{{ card.text }}</div>
|
|
216
228
|
|
|
217
229
|
<div class="buttons">
|
|
218
230
|
<template v-if="!card.user_can_access">
|
|
@@ -609,6 +621,8 @@ async function loadCampaigns(page = 1) {
|
|
|
609
621
|
max_keys_for_user: item.max_keys_for_user || 1,
|
|
610
622
|
platforms: (item.platforms || []).map((p: any) => p.name),
|
|
611
623
|
gameName: item.game?.name || null,
|
|
624
|
+
studioName: item.company?.name || null,
|
|
625
|
+
regions: (item.regions || []).map((r: any) => r.name).filter(Boolean),
|
|
612
626
|
userRequestSlug: null,
|
|
613
627
|
}
|
|
614
628
|
})
|
|
@@ -1090,12 +1104,24 @@ onUnmounted(() => {
|
|
|
1090
1104
|
gap: 12px;
|
|
1091
1105
|
button { height: 44px; }
|
|
1092
1106
|
|
|
1093
|
-
// FormAppSelect ships at 32px
|
|
1094
|
-
//
|
|
1095
|
-
//
|
|
1096
|
-
//
|
|
1097
|
-
|
|
1098
|
-
|
|
1107
|
+
// FormAppSelect ships at 32px with its own light --input-bg
|
|
1108
|
+
// (its default look on other pages, e.g. account settings
|
|
1109
|
+
// forms on a card background) — bump to match the 44px
|
|
1110
|
+
// filter pills/search box height, and repaint it to match
|
|
1111
|
+
// the dark toolbar instead of the light default, so it
|
|
1112
|
+
// reads as one consistent row instead of a native-looking
|
|
1113
|
+
// light select stranded next to dark pills.
|
|
1114
|
+
:deep(.select-wrapper) {
|
|
1115
|
+
display: flex;
|
|
1116
|
+
align-items: center;
|
|
1117
|
+
height: 44px;
|
|
1118
|
+
border-bottom-color: var(--secondary-info-fg);
|
|
1119
|
+
}
|
|
1120
|
+
:deep(.select-wrapper select) {
|
|
1121
|
+
height: 44px;
|
|
1122
|
+
background: transparent;
|
|
1123
|
+
color: var(--secondary-info-fg);
|
|
1124
|
+
}
|
|
1099
1125
|
}
|
|
1100
1126
|
|
|
1101
1127
|
.header-left .btn.tertiary.active {
|
|
@@ -1192,12 +1218,54 @@ onUnmounted(() => {
|
|
|
1192
1218
|
max-height: 174px;
|
|
1193
1219
|
min-height: 174px;
|
|
1194
1220
|
overflow: hidden;
|
|
1221
|
+
background: var(--body-bg-card, #101012);
|
|
1222
|
+
display: flex;
|
|
1223
|
+
align-items: center;
|
|
1224
|
+
justify-content: center;
|
|
1195
1225
|
img { width: 100%; height: auto; display: flex; }
|
|
1226
|
+
// The no-cover fallback is a small centered logo, not a
|
|
1227
|
+
// banner — stretching it to 100% width (the normal-cover
|
|
1228
|
+
// rule above) distorts it badly. Contain it instead.
|
|
1229
|
+
&:has(img[src$="no-cover-img.jpg"]) img {
|
|
1230
|
+
width: 40%;
|
|
1231
|
+
height: auto;
|
|
1232
|
+
max-height: 60%;
|
|
1233
|
+
object-fit: contain;
|
|
1234
|
+
}
|
|
1196
1235
|
}
|
|
1197
1236
|
}
|
|
1198
1237
|
}
|
|
1199
1238
|
|
|
1200
1239
|
.text {
|
|
1240
|
+
min-height: 210px;
|
|
1241
|
+
|
|
1242
|
+
.card-name {
|
|
1243
|
+
font-size: 16px;
|
|
1244
|
+
font-weight: 700;
|
|
1245
|
+
color: var(--title-fg);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.card-studio-row {
|
|
1249
|
+
font-size: 12px;
|
|
1250
|
+
color: var(--secondary-info-fg);
|
|
1251
|
+
font-weight: 400;
|
|
1252
|
+
display: flex;
|
|
1253
|
+
align-items: center;
|
|
1254
|
+
gap: 6px;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.card-availability {
|
|
1258
|
+
font-size: 12px;
|
|
1259
|
+
color: var(--key-accent, var(--primary, #D297FF));
|
|
1260
|
+
font-weight: 600;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.card-campaign-name {
|
|
1264
|
+
font-size: 13px;
|
|
1265
|
+
font-weight: 400;
|
|
1266
|
+
color: var(--secondary-info-fg);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1201
1269
|
.card-meta-row {
|
|
1202
1270
|
display: flex;
|
|
1203
1271
|
align-items: center;
|
|
@@ -1242,6 +1310,10 @@ onUnmounted(() => {
|
|
|
1242
1310
|
color: var(--secondary-info-fg);
|
|
1243
1311
|
text-transform: uppercase;
|
|
1244
1312
|
}
|
|
1313
|
+
.card-platform-more {
|
|
1314
|
+
border-style: dashed;
|
|
1315
|
+
opacity: 0.8;
|
|
1316
|
+
}
|
|
1245
1317
|
}
|
|
1246
1318
|
|
|
1247
1319
|
.buttons {
|