@opexa/portal-components 0.1.74 → 0.1.79

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 (89) hide show
  1. package/README.md +238 -0
  2. package/dist/client/hooks/useGlobalStore.d.ts +2 -8
  3. package/dist/client/hooks/useGlobalStore.js +1 -27
  4. package/dist/client/hooks/useMarkGameAsFavoriteMutation.js +9 -1
  5. package/dist/client/hooks/useSignInMutation.js +2 -7
  6. package/dist/client/hooks/useUnmarkGameAsFavoriteMutation.js +1 -1
  7. package/dist/client/hooks/useUpdateSessionMutation.js +1 -0
  8. package/dist/components/ForgotPassword/Crazywin/ForgotPassword.module.css +42 -42
  9. package/dist/components/Jackpots/Jackpots.module.css +288 -288
  10. package/dist/components/KYC/KYCDefault/KYCVerificationStatus.lazy.js +2 -2
  11. package/dist/components/KYC/KYCNonPagCor/KYCVerificationStatus.lazy.js +2 -2
  12. package/dist/components/PortalProvider/SessionWatcher.js +19 -22
  13. package/dist/components/Promos/PromoCardClassNames.d.ts +11 -0
  14. package/dist/components/Promos/PromoCardClassNames.js +6 -0
  15. package/dist/components/SignIn/CrazyWin/MobileNumberSignIn.js +1 -0
  16. package/dist/components/SignIn/CrazyWin/NameAndPasswordSignIn.js +1 -0
  17. package/dist/components/SignIn/HappyBingo/MobileNumberSignIn.js +1 -0
  18. package/dist/components/SignIn/HappyBingo/NameAndPasswordSignIn.js +1 -0
  19. package/dist/components/SignIn/MobileNumberSignIn.js +2 -5
  20. package/dist/components/SignIn/SignIn.js +5 -1
  21. package/dist/components/SignIn/SignIn.lazy.d.ts +1 -1
  22. package/dist/components/SignIn/SignIn.lazy.js +16 -12
  23. package/dist/components/SignUp/SignUpDefault/SignUpDefaultForm.js +13 -26
  24. package/dist/components/SignUp/SignUpDefault/SignUpDefaultFormInternational.js +4 -11
  25. package/dist/components/TermsOfUse/TermsOfUseV2.lazy.js +4 -1
  26. package/dist/components/Tournaments/TournamentsCarousel/TournamentsCarouselItem.module.css +184 -184
  27. package/dist/components/Tournaments/TournamentsList/TournamentItem.module.css +184 -184
  28. package/dist/constants/StorageKey.d.ts +1 -0
  29. package/dist/constants/StorageKey.js +1 -0
  30. package/dist/handlers/deleteSession.js +2 -1
  31. package/dist/handlers/getSession.js +17 -1
  32. package/dist/images/phone-icon.svg +10 -10
  33. package/dist/middleware.js +11 -2
  34. package/dist/schemas/forgotPasswordSchema.d.ts +4 -4
  35. package/dist/server/services/signOut.js +2 -1
  36. package/dist/services/account.d.ts +2 -6
  37. package/dist/services/account.js +0 -1
  38. package/dist/services/queries.d.ts +1 -1
  39. package/dist/services/queries.js +3613 -3617
  40. package/dist/styles/theme.css +776 -776
  41. package/dist/types/index.d.ts +3 -0
  42. package/dist/ui/AlertDialog/AlertDialog.d.ts +154 -154
  43. package/dist/ui/AlertDialog/alertDialog.recipe.d.ts +14 -14
  44. package/dist/ui/Badge/Badge.d.ts +12 -12
  45. package/dist/ui/Badge/badge.anatomy.d.ts +1 -1
  46. package/dist/ui/Badge/badge.recipe.d.ts +3 -3
  47. package/dist/ui/Carousel/Carousel.d.ts +99 -99
  48. package/dist/ui/Carousel/carousel.recipe.d.ts +11 -11
  49. package/dist/ui/Checkbox/Checkbox.d.ts +23 -23
  50. package/dist/ui/Checkbox/checkbox.recipe.d.ts +3 -3
  51. package/dist/ui/Clipboard/Clipboard.d.ts +18 -18
  52. package/dist/ui/Clipboard/clipboard.recipe.d.ts +3 -3
  53. package/dist/ui/Collapsible/Collapsible.d.ts +20 -20
  54. package/dist/ui/Collapsible/collapsible.recipe.d.ts +5 -5
  55. package/dist/ui/Combobox/Combobox.d.ts +42 -42
  56. package/dist/ui/Combobox/combobox.recipe.d.ts +3 -3
  57. package/dist/ui/DatePicker/DatePicker.d.ts +72 -72
  58. package/dist/ui/DatePicker/datePicker.recipe.d.ts +3 -3
  59. package/dist/ui/Dialog/Dialog.d.ts +33 -33
  60. package/dist/ui/Dialog/dialog.recipe.d.ts +3 -3
  61. package/dist/ui/Drawer/Drawer.d.ts +33 -33
  62. package/dist/ui/Drawer/drawer.recipe.d.ts +3 -3
  63. package/dist/ui/Menu/Menu.d.ts +144 -144
  64. package/dist/ui/Menu/menu.recipe.d.ts +8 -8
  65. package/dist/ui/Popover/Popover.d.ts +121 -121
  66. package/dist/ui/Popover/popover.recipe.d.ts +11 -11
  67. package/dist/ui/SegmentGroup/SegmentGroup.d.ts +18 -18
  68. package/dist/ui/SegmentGroup/segmentGroup.recipe.d.ts +3 -3
  69. package/dist/ui/Select/Select.d.ts +45 -45
  70. package/dist/ui/Select/select.recipe.d.ts +3 -3
  71. package/dist/ui/Table/Table.d.ts +21 -21
  72. package/dist/ui/Table/table.anatomy.d.ts +1 -1
  73. package/dist/ui/Table/table.recipe.d.ts +3 -3
  74. package/dist/ui/Tabs/Tabs.d.ts +15 -15
  75. package/dist/ui/Tabs/tabs.recipe.d.ts +3 -3
  76. package/dist/utils/session-invalid-reason.d.ts +3 -0
  77. package/dist/utils/session-invalid-reason.js +19 -0
  78. package/package.json +1 -1
  79. package/dist/components/BetDepositLimit/betDepositLimitStore.d.ts +0 -24
  80. package/dist/components/BetDepositLimit/betDepositLimitStore.js +0 -51
  81. package/dist/components/ResponsibleGamingLimits/ResponsibleGamingLimits.lazy.d.ts +0 -1
  82. package/dist/components/ResponsibleGamingLimits/ResponsibleGamingLimits.lazy.js +0 -117
  83. package/dist/components/UpdateMobilePhoneNumber/hooks/useAutoOpenWhenUnverified.d.ts +0 -6
  84. package/dist/components/UpdateMobilePhoneNumber/hooks/useAutoOpenWhenUnverified.js +0 -31
  85. package/dist/handlers/v4Cookies.d.ts +0 -9
  86. package/dist/handlers/v4Cookies.js +0 -49
  87. package/dist/images/game-providers/SEXYCASINO.webp +0 -0
  88. package/dist/utils/get-fingerprint.d.ts +0 -8
  89. package/dist/utils/get-fingerprint.js +0 -37
package/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # Opexa Portal Components
2
+
3
+ Reusable Next.js/React components, route handlers, middleware, hooks, services,
4
+ utilities, and theme assets for Opexa Portal member experiences.
5
+
6
+ **Package:** `@opexa/portal-components`
7
+ **Current version:** `0.1.64`
8
+ **Last updated:** 2026-08-08
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pnpm add @opexa/portal-components
14
+ ```
15
+
16
+ Install the required peer dependencies in the consuming Next app:
17
+
18
+ ```bash
19
+ pnpm add next@^15.4.0-canary.1 react@^19 react-dom@^19 \
20
+ @tanstack/react-query@^5 react-hook-form@^7 \
21
+ @hookform/resolvers@^5 zod@^3 tailwindcss@^4 \
22
+ @opexa/object-id@^0.1
23
+ pnpm add -D @tailwindcss/postcss@^4
24
+ ```
25
+
26
+ The package has no bare `@opexa/portal-components` export. Import published
27
+ subpaths only.
28
+
29
+ The published theme contains Tailwind 4 directives. Configure the consuming
30
+ app's PostCSS integration:
31
+
32
+ ```js
33
+ // postcss.config.mjs
34
+ export default {
35
+ plugins: {
36
+ '@tailwindcss/postcss': {},
37
+ },
38
+ };
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ Modules that import `@opexa/portal-components/constants` validate required
44
+ public environment variables at module load:
45
+
46
+ | Variable | Required | Description |
47
+ | --- | --- | --- |
48
+ | `NEXT_PUBLIC_SITE_ID` | Yes | CMS site ID sent to portal APIs. |
49
+ | `NEXT_PUBLIC_PLATFORM_ID` | Yes | CMS site platform ID sent to portal APIs. |
50
+ | `NEXT_PUBLIC_PLATFORM_CODE` | Yes | Portal platform code sent in API headers. |
51
+ | `NEXT_PUBLIC_ENVIRONMENT` | No | `development`, `staging`, or `production`; defaults to `development`. |
52
+ | `NEXT_PUBLIC_AUTH_API_BASE_URL` | No | Overrides the auth service base URL for every environment. |
53
+ | `NEXT_PUBLIC_REPORT_WEB_VITALS` | No | Enables internal web-vitals reporting for any non-empty value, including the string `false`. |
54
+
55
+ ## Quick start
56
+
57
+ Use the shared Next config to include supported image hosts and the experimental
58
+ Node middleware setting:
59
+
60
+ ```ts
61
+ // next.config.ts
62
+ import { extendConfig } from '@opexa/portal-components/next.config';
63
+
64
+ export default extendConfig({});
65
+ ```
66
+
67
+ Import the published theme stylesheet and wrap app content with the server
68
+ component provider:
69
+
70
+ ```tsx
71
+ // app/layout.tsx
72
+ import type { ReactNode } from 'react';
73
+ import '@opexa/portal-components/theme.css';
74
+ import { PortalProvider } from '@opexa/portal-components/PortalProvider';
75
+
76
+ export default function RootLayout({ children }: { children: ReactNode }) {
77
+ return (
78
+ <html lang="en">
79
+ <body>
80
+ <PortalProvider>{children}</PortalProvider>
81
+ </body>
82
+ </html>
83
+ );
84
+ }
85
+ ```
86
+
87
+ All `PortalProvider` options are optional:
88
+
89
+ | Prop | Purpose |
90
+ | --- | --- |
91
+ | `recaptchaSiteKey` | Configures the reCAPTCHA provider. |
92
+ | `googleFraudDefenseSiteKey` | Stores the Google Fraud Defense key for client requests. |
93
+ | `isNonRegulated` | Enables non-regulated portal behavior. |
94
+ | `enablePushNotif` | Enables native push notification setup; defaults to `true`. |
95
+
96
+ Mount the auth/session route handlers in a catch-all App Router route:
97
+
98
+ ```ts
99
+ // app/api/[...auth]/route.ts
100
+ export * from '@opexa/portal-components/handlers';
101
+ ```
102
+
103
+ Enable access-token refresh middleware:
104
+
105
+ ```ts
106
+ // middleware.ts
107
+ export { middleware } from '@opexa/portal-components/middleware';
108
+
109
+ export const config = {
110
+ runtime: 'nodejs',
111
+ matcher:
112
+ '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
113
+ };
114
+ ```
115
+
116
+ ## Package exports
117
+
118
+ | Import path | Contents |
119
+ | --- | --- |
120
+ | `@opexa/portal-components/*` | Feature components from `src/lib/components/*`, such as `SignIn`, `Games`, `DepositWithdrawal`, `PortalProvider`, and `Protected`. |
121
+ | `@opexa/portal-components/ui/*` | UI primitives from `src/lib/ui/*`, such as `Button`, `Dialog`, `Select`, `Tabs`, and `Tooltip`. |
122
+ | `@opexa/portal-components/types` | Shared session, query/mutation, pagination, game, wallet, promo, KYC, and API response types. |
123
+ | `@opexa/portal-components/utils/*` | Utility modules including query keys, formatting, polling, validators, image/provider helpers, and mobile helpers. |
124
+ | `@opexa/portal-components/handlers` | `GET`, `POST`, `PATCH`, and `DELETE` handlers for auth/session API routes. |
125
+ | `@opexa/portal-components/middleware` | Access-token refresh middleware and `extendMiddleware`. |
126
+ | `@opexa/portal-components/constants` | Environment, endpoint, game, header, secret-question, and storage constants. |
127
+ | `@opexa/portal-components/services/*` | GraphQL/REST service functions. |
128
+ | `@opexa/portal-components/client-hooks/*` | TanStack Query hooks, mutations, stores, and browser-only hooks. |
129
+ | `@opexa/portal-components/client-services/*` | Browser auth/session helpers. |
130
+ | `@opexa/portal-components/client-utils/*` | Browser UI/native helpers. |
131
+ | `@opexa/portal-components/server-services/*` | Server auth/session helpers. |
132
+ | `@opexa/portal-components/server-utils/*` | Server React Query prefetch helpers. |
133
+ | `@opexa/portal-components/next.config` | Shared Next config and `extendConfig`. |
134
+ | `@opexa/portal-components/theme.css` | Published theme stylesheet. |
135
+
136
+ ## Auth/session API handlers
137
+
138
+ `@opexa/portal-components/handlers` dispatches by the first `auth` path segment
139
+ in `app/api/[...auth]/route.ts`.
140
+
141
+ Successful responses use `{ ok: true, data? }`. Handled failures use
142
+ `{ ok: false, message, __error? }`. Session cookies are HTTP-only with
143
+ `SameSite=Strict`.
144
+
145
+ | Method | Path | Request and response |
146
+ | --- | --- | --- |
147
+ | `GET` | `/api/health-check` | Returns the request user agent with no-cache and CORS headers. |
148
+ | `GET` | `/api/sessions` | Returns an authenticated or unauthenticated `Session`; refreshes an absent access token when a refresh token is valid. |
149
+ | `POST` | `/api/sessions` | Accepts a sign-in body described below. Returns an authenticator challenge, or sets access/refresh cookies and responds with HTTP 201. |
150
+ | `PATCH` | `/api/sessions` | Accepts `{ domain?: string \| null, btag?: string \| null }`; truthy values set 30-day cookies. Null or empty values do not clear existing cookies. |
151
+ | `DELETE` | `/api/sessions` | Destroys the upstream session when an access token exists, then clears access and refresh cookies. |
152
+ | `DELETE` | `/api/refresh-sessions` | Clears only the access-token cookie so the session can be refreshed. |
153
+ | `POST` | `/api/authenticate` | Accepts `{ type: 'SECURITY_QUESTION', token, secretAnswer }`; success sets access/refresh cookies and returns HTTP 201. |
154
+ | `POST` | `/api/token` | Creates a single-use token and returns it as `data` with HTTP 201. |
155
+
156
+ `POST /api/sessions` accepts one of these JSON bodies:
157
+
158
+ ```ts
159
+ type CreateSessionInput =
160
+ | { type: 'NAME_AND_PASSWORD'; name: string; password: string }
161
+ | {
162
+ type: 'MOBILE_NUMBER';
163
+ mobileNumber: string;
164
+ verificationCode: string;
165
+ }
166
+ | { type: 'MAYA'; session: string }
167
+ | {
168
+ type: 'SOCIALS' | 'CABINET' | 'SINGLE_USE_TOKEN';
169
+ token: string;
170
+ };
171
+ ```
172
+
173
+ Session creation forwards domain, fingerprint, platform-web, channel,
174
+ user-agent, and forwarded-IP headers. `X-Session-Version` and `X-Api-Version`
175
+ select the upstream session/API variants. Authentication forwards domain,
176
+ channel, user-agent, and forwarded-IP headers. For both routes,
177
+ `X-Recaptcha-Token` takes precedence over `Google-Recaptcha-Response`, and the
178
+ selected value is sent upstream as `Google-Recaptcha-Response`. Validation
179
+ failures return HTTP 400; handled authentication/service failures return HTTP
180
+ 401. Malformed JSON and uncaught upstream errors use Next.js error handling.
181
+
182
+ Unknown paths return `{ ok: false, message: 'Not found' }` with HTTP 404.
183
+
184
+ ## Idle logout worker
185
+
186
+ `IdleLogout` and `useIdleLogout` create a worker at `/idle-worker.js`. The
187
+ package publishes only `dist`, so consumers using idle logout must provide this
188
+ file in their app's `public` directory:
189
+
190
+ ```js
191
+ let timeout;
192
+
193
+ self.onmessage = (event) => {
194
+ const { command, idleTime } = event.data;
195
+
196
+ if (command === 'start' || command === 'reset') {
197
+ clearTimeout(timeout);
198
+ timeout = setTimeout(() => self.postMessage('idle'), idleTime);
199
+ }
200
+ };
201
+ ```
202
+
203
+ ## Feature overview
204
+
205
+ - Provider shell: `PortalProvider`, `Protected`, `IdleLogout`, `TopProgress`,
206
+ and search/session helpers.
207
+ - Authentication and onboarding: sign-in, sign-up, password recovery, affiliate
208
+ and agent sign-up/sign-in, migration, and onboarding flows.
209
+ - Account and compliance: account pages, KYC, verification, security,
210
+ responsible-gaming limits, and bet/deposit limits.
211
+ - Payments: deposit/withdrawal flows and GCash/Maya/deposit-status screens.
212
+ - Games and rewards: game listings/launch, favorites, recent games, top wins,
213
+ promos, bonuses, cashback, quests, tournaments, and jackpots.
214
+ - Content/legal: banners, marquees, messages, news, disclaimers, terms, and
215
+ terms of use.
216
+
217
+ ## Development
218
+
219
+ From the repository root:
220
+
221
+ ```bash
222
+ pnpm run portal-components lint
223
+ pnpm run portal-components test
224
+ pnpm run portal-components build
225
+ pnpm run portal-components build:app
226
+ ```
227
+
228
+ Library source lives under `src/lib`. The `src/app` tree is a Next.js demo/dev
229
+ app and is not part of the package entry surface.
230
+
231
+ Consumer-facing changes require a rebuild, version bump, and republish. The
232
+ `release` script creates a release commit and tag, builds the library, and
233
+ publishes to npm; run it only for an explicitly authorized release.
234
+
235
+ ## Documentation
236
+
237
+ - [Portal Components codemap](../../docs/CODEMAPS/portal-components/INDEX.md)
238
+ - [Repository codemap index](../../docs/CODEMAPS/INDEX.md)
@@ -1,15 +1,9 @@
1
- import type { FavoriteGame, Game, GameSession, Message, SignInType } from '../../types';
1
+ import type { FavoriteGame, Game, GameSession, Message } from '../../types';
2
2
  export interface PopupStore {
3
3
  open: boolean;
4
4
  setOpen: (open: boolean) => void;
5
5
  '~touched': boolean;
6
6
  }
7
- export interface SignInStore extends PopupStore {
8
- prefillMobileNumber: string | null;
9
- setPrefillMobileNumber: (value: string | null) => void;
10
- pendingType: SignInType | null;
11
- setPendingType: (type: SignInType | null) => void;
12
- }
13
7
  export type GameLaunchStore__Details = {
14
8
  status: 'WAITING';
15
9
  game?: null;
@@ -92,7 +86,7 @@ export interface BetDepositLimitReachedStore extends PopupStore {
92
86
  }
93
87
  export interface GlobalStore {
94
88
  signUp: PopupStore;
95
- signIn: SignInStore;
89
+ signIn: PopupStore;
96
90
  forgotPassword: PopupStore;
97
91
  disclaimer: PopupStore;
98
92
  depositWithdrawal: DepositWithdrawalStore;
@@ -12,26 +12,6 @@ export const useGlobalStore = create()(devtools(subscribeWithSelector((set) => (
12
12
  },
13
13
  }));
14
14
  },
15
- prefillMobileNumber: null,
16
- setPrefillMobileNumber(value) {
17
- set((prev) => ({
18
- signIn: {
19
- ...prev.signIn,
20
- prefillMobileNumber: value,
21
- '~touched': true,
22
- },
23
- }));
24
- },
25
- pendingType: null,
26
- setPendingType(type) {
27
- set((prev) => ({
28
- signIn: {
29
- ...prev.signIn,
30
- pendingType: type,
31
- '~touched': true,
32
- },
33
- }));
34
- },
35
15
  '~touched': false,
36
16
  },
37
17
  signUp: {
@@ -642,13 +622,7 @@ export const useGlobalStore = create()(devtools(subscribeWithSelector((set) => (
642
622
  set((state) => ({
643
623
  ...state,
644
624
  signUp: { ...state.signUp, open: false, '~touched': false },
645
- signIn: {
646
- ...state.signIn,
647
- open: false,
648
- prefillMobileNumber: null,
649
- pendingType: null,
650
- '~touched': false,
651
- },
625
+ signIn: { ...state.signIn, open: false, '~touched': false },
652
626
  forgotPassword: {
653
627
  ...state.forgotPassword,
654
628
  open: false,
@@ -28,7 +28,15 @@ export const useMarkGameAsFavoriteMutation = (config) => {
28
28
  const game = games.find((game) => game.reference === id);
29
29
  queryClient.setQueryData(getFavoriteGamesQueryKey(), (prev = []) => {
30
30
  if (game) {
31
- return [...prev, game];
31
+ return [
32
+ ...prev,
33
+ {
34
+ id: game.reference,
35
+ name: game.name,
36
+ type: game.type,
37
+ provider: game.provider,
38
+ },
39
+ ];
32
40
  }
33
41
  });
34
42
  return id;
@@ -21,12 +21,7 @@ export const useSignInMutation = (options) => {
21
21
  mutationFn: async (input) => {
22
22
  const session = await getSession();
23
23
  const token = await executeRecaptcha('submit');
24
- const isInplay = versionSession === 'Inplay' &&
25
- (input.type === 'NAME_AND_PASSWORD' ||
26
- (input.type === 'MOBILE_NUMBER' && featureFlagEnabled));
27
- const fingerprint = (input.type === 'NAME_AND_PASSWORD' &&
28
- (versionSession === 'Inplay' || version !== 1)) ||
29
- (input.type === 'MOBILE_NUMBER' && isInplay)
24
+ const fingerprint = version !== 1
30
25
  ? await getFingerprint()
31
26
  : null;
32
27
  const recaptchaHeaderKey = isGoogleFraudDefense
@@ -48,7 +43,7 @@ export const useSignInMutation = (options) => {
48
43
  : { Channel: 'IOS' }),
49
44
  },
50
45
  }, versionSession, version, authApiBaseUrl, featureFlagEnabled);
51
- const authenticator = isInplay
46
+ const authenticator = (versionSession === 'Inplay' && input.type === 'NAME_AND_PASSWORD')
52
47
  ? await pollSignIn(signInOnce)
53
48
  : await signInOnce();
54
49
  if (!authenticator) {
@@ -19,7 +19,7 @@ export const useUnmarkGameAsFavoriteMutation = (config) => {
19
19
  },
20
20
  });
21
21
  queryClient.setQueryData(getFavoriteGamesQueryKey(), (prev) => {
22
- return prev?.filter((game) => game.reference !== id);
22
+ return prev?.filter((game) => game.id !== id);
23
23
  });
24
24
  },
25
25
  });
@@ -20,6 +20,7 @@ export const useUpdateSessionMutation = (config) => {
20
20
  status: 'unauthenticated',
21
21
  domain,
22
22
  btag,
23
+ reason: prev.reason,
23
24
  };
24
25
  }
25
26
  return {
@@ -1,43 +1,43 @@
1
- .card {
2
- --gradient-bg: radial-gradient(103.87% 100% at 50.15% 0%, #072b37 20.3%, #051125 100%);
3
-
4
- --gradient-border-top: radial-gradient(
5
- 139.23% 113.41% at 50.14% 0%,
6
- #8affdc 0.49%,
7
- rgba(138, 255, 220, 0) 33.03%
8
- );
9
-
10
- --gradient-border-bottom: radial-gradient(
11
- 42.09% 54.21% at 50.14% 100%,
12
- #8affdc 5.74%,
13
- rgba(138, 255, 220, 0) 93.84%
14
- );
15
-
16
- position: relative;
17
- background: var(--gradient-bg);
18
- box-shadow: 0px 4px 14px 6px rgba(0, 0, 0, 0.6);
19
- backdrop-filter: blur(25px);
20
- border-radius: 1.25em;
21
- border: 1px solid rgba(255, 255, 255, 0.1);
22
- }
23
-
24
- .card::after,
25
- .card::before {
26
- content: '';
27
- position: absolute;
28
- top: 0;
29
- left: 0;
30
- width: 100%;
31
- height: 100%;
32
- z-index: -1;
33
- border: 0.5px solid transparent;
34
- border-image-slice: 1;
35
- }
36
-
37
- .card::before {
38
- border-image-source: var(--gradient-border-bottom);
39
- }
40
-
41
- .card::after {
42
- border-image-source: var(--gradient-border-top);
1
+ .card {
2
+ --gradient-bg: radial-gradient(103.87% 100% at 50.15% 0%, #072b37 20.3%, #051125 100%);
3
+
4
+ --gradient-border-top: radial-gradient(
5
+ 139.23% 113.41% at 50.14% 0%,
6
+ #8affdc 0.49%,
7
+ rgba(138, 255, 220, 0) 33.03%
8
+ );
9
+
10
+ --gradient-border-bottom: radial-gradient(
11
+ 42.09% 54.21% at 50.14% 100%,
12
+ #8affdc 5.74%,
13
+ rgba(138, 255, 220, 0) 93.84%
14
+ );
15
+
16
+ position: relative;
17
+ background: var(--gradient-bg);
18
+ box-shadow: 0px 4px 14px 6px rgba(0, 0, 0, 0.6);
19
+ backdrop-filter: blur(25px);
20
+ border-radius: 1.25em;
21
+ border: 1px solid rgba(255, 255, 255, 0.1);
22
+ }
23
+
24
+ .card::after,
25
+ .card::before {
26
+ content: '';
27
+ position: absolute;
28
+ top: 0;
29
+ left: 0;
30
+ width: 100%;
31
+ height: 100%;
32
+ z-index: -1;
33
+ border: 0.5px solid transparent;
34
+ border-image-slice: 1;
35
+ }
36
+
37
+ .card::before {
38
+ border-image-source: var(--gradient-border-bottom);
39
+ }
40
+
41
+ .card::after {
42
+ border-image-source: var(--gradient-border-top);
43
43
  }