@meycult/core 0.1.0 → 0.2.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/package.json +3 -6
- package/src/brand/BrandName.tsx +4 -2
- package/src/brand/LaunchButton.tsx +5 -3
- package/src/brand/Logo.tsx +25 -6
- package/src/brand/PyramidName.tsx +10 -0
- package/src/brand/Sigil.tsx +10 -5
- package/src/brand/TokenIcons.tsx +12 -6
- package/src/brand.ts +4 -1
- package/src/components/CultCountdown.tsx +89 -14
- package/src/game/categories.ts +23 -0
- package/src/game/cults.ts +88 -0
- package/src/game/quests.ts +62 -0
- package/src/game/types.ts +27 -0
- package/src/index.ts +64 -21
- package/src/product.ts +17 -0
- package/src/styles/theme.css +97 -6
- package/src/tokens.ts +10 -2
- package/src/ui/CultButton.tsx +55 -0
- package/src/ui/RarityBadge.tsx +18 -0
- package/src/ui/animated-shiny-text.tsx +2 -2
- package/src/ui/badges.tsx +43 -0
- package/src/ui/card.tsx +20 -92
- package/src/ui/shimmer-button.tsx +5 -3
- package/src/ui/virtues.tsx +66 -0
- package/src/unveil/LaunchStage.tsx +3 -2
- package/src/unveil/bg.ts +20 -11
- package/src/unveil/bgStyles.ts +0 -1
- package/src/unveil/blocks.tsx +51 -14
- package/src/unveil/effects.tsx +182 -86
- package/src/unveil.ts +2 -21
- package/src/auth/Gate.tsx +0 -14
- package/src/auth/types.ts +0 -12
- package/src/brand/GodEmperor.tsx +0 -8
- package/src/cn.ts +0 -6
- package/src/lib/images.ts +0 -10
- package/src/lib/money.ts +0 -12
- package/src/lib/store.ts +0 -19
- package/src/shell/types.ts +0 -13
- package/src/supabase/browser.ts +0 -103
- package/src/supabase/callback.ts +0 -15
- package/src/supabase/proxy.ts +0 -36
- package/src/supabase/server.ts +0 -31
- package/src/ui/Callout.tsx +0 -33
package/src/index.ts
CHANGED
|
@@ -1,22 +1,82 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Generic — single-sourced in @meyvoid/core, re-exported here so hosts
|
|
2
|
+
// keep one import root. Do NOT re-add local copies (the sin is the second copy).
|
|
3
|
+
export { cn } from '@meyvoid/core/cn'
|
|
4
|
+
export type { NavLink, LinkComponent } from '@meyvoid/core'
|
|
5
|
+
export {
|
|
6
|
+
createSupabaseBrowser,
|
|
7
|
+
chunkedCookieStorage,
|
|
8
|
+
exchangeCodeForSession,
|
|
9
|
+
createPersistStore,
|
|
10
|
+
RequireAuth,
|
|
11
|
+
formatPrice,
|
|
12
|
+
isAbsoluteUrl,
|
|
13
|
+
resolveImage,
|
|
14
|
+
Callout,
|
|
15
|
+
Tooltip,
|
|
16
|
+
TabBar,
|
|
17
|
+
ChipGroup,
|
|
18
|
+
DropdownFilter,
|
|
19
|
+
StatBar,
|
|
20
|
+
} from '@meyvoid/core'
|
|
21
|
+
export type {
|
|
22
|
+
BrowserOptions,
|
|
23
|
+
BrowserEnv,
|
|
24
|
+
GateUser as SessionProfile,
|
|
25
|
+
CalloutProps,
|
|
26
|
+
CalloutType,
|
|
27
|
+
} from '@meyvoid/core'
|
|
28
|
+
// NOTE: server-only helpers (next/headers, next/server) live in @meyvoid/core
|
|
29
|
+
// but are intentionally NOT re-exported — the barrel ships to client
|
|
30
|
+
// components. Deep-import from void:
|
|
31
|
+
// import { createSupabaseServerClient } from '@meyvoid/core/supabase/server'
|
|
32
|
+
// import { updateSession } from '@meyvoid/core/supabase/proxy'
|
|
33
|
+
export { COLORS, GLOW, GRADIENTS, FONTS } from './tokens'
|
|
3
34
|
export { BRAND } from './brand'
|
|
35
|
+
export { PRODUCT, BAZAAR } from './product'
|
|
4
36
|
|
|
5
37
|
export { default as Sigil } from './brand/Sigil'
|
|
6
38
|
export { default as CultEye, GazeProvider, useGaze } from './brand/CultEye'
|
|
7
39
|
export { default as Logo } from './brand/Logo'
|
|
8
40
|
export { default as BrandName } from './brand/BrandName'
|
|
41
|
+
export { default as PyramidName } from './brand/PyramidName'
|
|
9
42
|
export {
|
|
10
43
|
InfluenceIcon,
|
|
11
44
|
FateIcon,
|
|
12
45
|
InfluenceText,
|
|
13
46
|
FateText,
|
|
47
|
+
MeyInfluence,
|
|
48
|
+
MeyFate,
|
|
14
49
|
} from './brand/TokenIcons'
|
|
50
|
+
export type { TokenIconProps } from './brand/TokenIcons'
|
|
15
51
|
export { default as LaunchButton } from './brand/LaunchButton'
|
|
16
52
|
export { default as CornerGlow } from './brand/CornerGlow'
|
|
17
|
-
export { default as GodEmperorBrand } from './brand/GodEmperor'
|
|
18
53
|
|
|
19
54
|
export { Card } from './ui/card'
|
|
55
|
+
export type { CardProps, CardVariant } from './ui/card'
|
|
56
|
+
export { CultButton } from './ui/CultButton'
|
|
57
|
+
export type { CultButtonProps, CultButtonVariant, CultButtonSize } from './ui/CultButton'
|
|
58
|
+
export { RarityBadge } from './ui/RarityBadge'
|
|
59
|
+
export { TierBadge, BadgeCategoryChip, BADGE_CATEGORY_ICONS, RESULT_COLORS } from './ui/badges'
|
|
60
|
+
export { VIRTUE_META, TIER_NAMES, getVirtueTier, VirtueBar } from './ui/virtues'
|
|
61
|
+
export type {
|
|
62
|
+
VirtueName,
|
|
63
|
+
Virtues,
|
|
64
|
+
VirtueXP,
|
|
65
|
+
VirtueTierLevel,
|
|
66
|
+
VirtueTierLabel,
|
|
67
|
+
CultId,
|
|
68
|
+
Category,
|
|
69
|
+
HeroType,
|
|
70
|
+
PredictionResult,
|
|
71
|
+
Rarity,
|
|
72
|
+
BadgeTier,
|
|
73
|
+
BadgeCategory,
|
|
74
|
+
} from './game/types'
|
|
75
|
+
export { CULT_META, CULT_ORDER, VIRTUE_ORDER, CULT_COLORS, FALLBACK_CULT, getCult } from './game/cults'
|
|
76
|
+
export type { CultMeta } from './game/cults'
|
|
77
|
+
export { CATEGORY_COLORS, CATEGORY_LABELS, HERO_TYPE_LABELS } from './game/categories'
|
|
78
|
+
export { rarityTier, formatVolume, formatDeadline, formatDeadlineShort } from './game/quests'
|
|
79
|
+
export type { DensityTier, Deadline } from './game/quests'
|
|
20
80
|
export { CultCountdown } from './components/CultCountdown'
|
|
21
81
|
export {
|
|
22
82
|
UNVEIL_AT,
|
|
@@ -36,26 +96,9 @@ export {
|
|
|
36
96
|
HypeRow,
|
|
37
97
|
NotifyPanel,
|
|
38
98
|
} from './unveil/blocks'
|
|
39
|
-
export type { WhereWhenProps, UnveilCard } from './unveil/blocks'
|
|
99
|
+
export type { WhereWhenProps, UnveilCard, UnveilHeroSizes, UnveilHeroRoutes, HypeRowRoutes } from './unveil/blocks'
|
|
40
100
|
export { TabletPanel } from './unveil/Tablet'
|
|
41
101
|
export { useUnveilBg, BG_EFFECTS, BG_DEFAULTS, parseBgQuery } from './unveil/bg'
|
|
42
102
|
export type { BgEffect } from './unveil/bg'
|
|
43
103
|
export { ShimmerButton } from './ui/shimmer-button'
|
|
44
104
|
export { AnimatedShinyText } from './ui/animated-shiny-text'
|
|
45
|
-
export { Callout } from './ui/Callout'
|
|
46
|
-
|
|
47
|
-
export type { NavLink, LinkComponent } from './shell/types'
|
|
48
|
-
|
|
49
|
-
export { createSupabaseBrowser, chunkedCookieStorage } from './supabase/browser'
|
|
50
|
-
// NOTE: server-only helpers (next/headers, next/server) are intentionally
|
|
51
|
-
// NOT re-exported here — the barrel ships to client components. Deep-import:
|
|
52
|
-
// import { createSupabaseServerClient } from '@meycult/core/supabase/server'
|
|
53
|
-
// import { updateSession } from '@meycult/core/supabase/proxy'
|
|
54
|
-
// import { exchangeCodeForSession } from '@meycult/core/supabase/callback'
|
|
55
|
-
|
|
56
|
-
export type { SessionProfile } from './auth/types'
|
|
57
|
-
export { RequireAuth } from './auth/Gate'
|
|
58
|
-
|
|
59
|
-
export { formatPrice } from './lib/money'
|
|
60
|
-
export { isAbsoluteUrl, resolveImage } from './lib/images'
|
|
61
|
-
export { createPersistStore } from './lib/store'
|
package/src/product.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Pyramid — the MeyCult game product (app). Master brand stays BRAND (MeyCult);
|
|
2
|
+
* Pyramid is the product lockup, domain, and Bazaar sibling reference. */
|
|
3
|
+
|
|
4
|
+
export const PRODUCT = {
|
|
5
|
+
name: 'Pyramid',
|
|
6
|
+
tagline: 'Enter the Pyramid',
|
|
7
|
+
description: 'The MeyCult prediction arena. Wager MeyFate, grow MeyInfluence, ascend the virtues.',
|
|
8
|
+
domain: 'pyramid.meycult.com',
|
|
9
|
+
} as const
|
|
10
|
+
|
|
11
|
+
/** Bazaar — the MeyCult shop product (store). Display name; shop.meycult.com follows later. */
|
|
12
|
+
export const BAZAAR = {
|
|
13
|
+
name: 'Bazaar',
|
|
14
|
+
tagline: 'Relics of the Cult',
|
|
15
|
+
description: 'Supporter packs and MeyCult merch. Every purchase funds development.',
|
|
16
|
+
domain: 'shop.meycult.com',
|
|
17
|
+
} as const
|
package/src/styles/theme.css
CHANGED
|
@@ -323,12 +323,24 @@ h2 {
|
|
|
323
323
|
color: var(--color-accent);
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
/* ──
|
|
326
|
+
/* ── Yellow glow behind Unveil, same treatment as MeyCult wordmark ── */
|
|
327
327
|
.unveil-glow {
|
|
328
328
|
text-shadow:
|
|
329
|
-
0 0 40px rgba(255,
|
|
330
|
-
0 0 80px rgba(255,
|
|
331
|
-
0 0 120px rgba(255,
|
|
329
|
+
0 0 40px rgba(255, 230, 0, 0.35),
|
|
330
|
+
0 0 80px rgba(255, 230, 0, 0.15),
|
|
331
|
+
0 0 120px rgba(255, 230, 0, 0.08);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* ── Countdown bob: gentle levitation for the DD:HH:MM:SS block ── */
|
|
335
|
+
@keyframes countdown-bob {
|
|
336
|
+
0%, 100% { transform: translateY(0); }
|
|
337
|
+
50% { transform: translateY(-7px); }
|
|
338
|
+
}
|
|
339
|
+
.countdown-bob {
|
|
340
|
+
animation: countdown-bob 4s ease-in-out infinite;
|
|
341
|
+
}
|
|
342
|
+
@media (prefers-reduced-motion: reduce) {
|
|
343
|
+
.countdown-bob { animation: none; }
|
|
332
344
|
}
|
|
333
345
|
|
|
334
346
|
/* ── Unveil tablet: yellow stone, engraved glyph frieze ── */
|
|
@@ -336,9 +348,9 @@ h2 {
|
|
|
336
348
|
position: relative;
|
|
337
349
|
border-radius: 18px;
|
|
338
350
|
border: 2px solid rgba(255, 230, 0, 0.35);
|
|
339
|
-
background: linear-gradient(180deg,
|
|
351
|
+
background: linear-gradient(180deg, rgba(42, 32, 3, 0.33) 0%, rgba(23, 20, 2, 0.33) 100%);
|
|
340
352
|
box-shadow:
|
|
341
|
-
inset 0 2px 24px rgba(0, 0, 0, 0.
|
|
353
|
+
inset 0 2px 24px rgba(0, 0, 0, 0.22),
|
|
342
354
|
inset 0 -1px 0 rgba(255, 247, 161, 0.18),
|
|
343
355
|
0 0 60px rgba(255, 230, 0, 0.25);
|
|
344
356
|
padding: 2rem 1.5rem;
|
|
@@ -394,3 +406,82 @@ h2 {
|
|
|
394
406
|
.table-glass tr:last-child td {
|
|
395
407
|
border-bottom: none;
|
|
396
408
|
}
|
|
409
|
+
|
|
410
|
+
/* Pyramid — product wordmark (the game). New Rocker accent, brand glow. */
|
|
411
|
+
.brand-pyramid {
|
|
412
|
+
font-family: var(--font-cult);
|
|
413
|
+
color: var(--color-accent);
|
|
414
|
+
text-shadow: 0 0 40px rgba(255,230,0,0.35), 0 0 80px rgba(255,230,0,0.18), 0 0 120px rgba(255,230,0,0.08);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/* Glass card hover variants — the app's canonical Card API (glow/clarity/depth/reflect). */
|
|
418
|
+
/* Glass card — clarity deepen on hover */
|
|
419
|
+
.card-clarity {
|
|
420
|
+
transition: background 0.4s, border-color 0.4s, box-shadow 0.4s, transform 0.4s, backdrop-filter 0.4s;
|
|
421
|
+
}
|
|
422
|
+
.card-clarity:hover {
|
|
423
|
+
background: rgba(255, 255, 255, 0.14);
|
|
424
|
+
backdrop-filter: blur(24px);
|
|
425
|
+
-webkit-backdrop-filter: blur(24px);
|
|
426
|
+
border-color: rgba(255, 255, 255, 0.25);
|
|
427
|
+
transform: translateY(-4px);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* Glass card — edge glow on hover */
|
|
431
|
+
.card-glow {
|
|
432
|
+
transition: background 0.4s, border-color 0.4s, box-shadow 0.4s, transform 0.4s;
|
|
433
|
+
}
|
|
434
|
+
.card-glow:hover {
|
|
435
|
+
border-color: rgba(255, 230, 0, 0.4);
|
|
436
|
+
box-shadow: 0 0 0 1px rgba(255, 230, 0, 0.2),
|
|
437
|
+
0 0 24px rgba(255, 230, 0, 0.25),
|
|
438
|
+
inset 0 0 24px rgba(255, 230, 0, 0.05);
|
|
439
|
+
background: rgba(255, 230, 0, 0.05);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/* Glass card — depth lift on hover */
|
|
443
|
+
.card-depth {
|
|
444
|
+
transition: background 0.4s, border-color 0.4s, box-shadow 0.4s, transform 0.4s;
|
|
445
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
446
|
+
}
|
|
447
|
+
.card-depth:hover {
|
|
448
|
+
transform: translateY(-8px);
|
|
449
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2), 0 16px 40px rgba(0, 0, 0, 0.4), 0 32px 60px rgba(0, 0, 0, 0.2);
|
|
450
|
+
background: rgba(255, 255, 255, 0.10);
|
|
451
|
+
border-color: rgba(255, 255, 255, 0.18);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* Glass card — reflective sweep on hover */
|
|
455
|
+
.card-reflect {
|
|
456
|
+
position: relative;
|
|
457
|
+
overflow: hidden;
|
|
458
|
+
transition: background 0.4s, border-color 0.4s, transform 0.4s;
|
|
459
|
+
}
|
|
460
|
+
.card-reflect::before {
|
|
461
|
+
content: '';
|
|
462
|
+
position: absolute;
|
|
463
|
+
top: -50%;
|
|
464
|
+
left: -60%;
|
|
465
|
+
width: 40%;
|
|
466
|
+
height: 200%;
|
|
467
|
+
background: linear-gradient(105deg, transparent, rgba(255, 255, 255, 0.12), transparent);
|
|
468
|
+
transform: skewX(-20deg);
|
|
469
|
+
transition: left 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
470
|
+
pointer-events: none;
|
|
471
|
+
}
|
|
472
|
+
.card-reflect:hover::before {
|
|
473
|
+
left: 120%;
|
|
474
|
+
}
|
|
475
|
+
.card-reflect:hover {
|
|
476
|
+
background: rgba(255, 255, 255, 0.10);
|
|
477
|
+
border-color: rgba(255, 255, 255, 0.20);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
@media (prefers-reduced-motion: reduce) {
|
|
481
|
+
.card-clarity, .card-glow, .card-depth, .card-reflect {
|
|
482
|
+
transition: none !important;
|
|
483
|
+
}
|
|
484
|
+
.card-reflect::before {
|
|
485
|
+
transition: none !important;
|
|
486
|
+
}
|
|
487
|
+
}
|
package/src/tokens.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// TS mirror of src/styles/theme.css @theme tokens — for canvas/tests/Node use.
|
|
2
|
+
// CSS wins on conflict: bg #141003 + yesBg 0.21 locked (P0).
|
|
2
3
|
|
|
3
4
|
export const COLORS = {
|
|
4
|
-
bg: '#
|
|
5
|
+
bg: '#141003',
|
|
5
6
|
surface: '#171402',
|
|
6
7
|
panel: 'rgba(26, 22, 3, 0.55)',
|
|
7
8
|
accent: '#FFE600',
|
|
@@ -11,7 +12,7 @@ export const COLORS = {
|
|
|
11
12
|
line: 'rgba(255, 230, 0, 0.18)',
|
|
12
13
|
yes: '#FFE600',
|
|
13
14
|
no: '#e04a4a',
|
|
14
|
-
yesBg: 'rgba(255, 230, 0, 0.
|
|
15
|
+
yesBg: 'rgba(255, 230, 0, 0.21)',
|
|
15
16
|
noBg: 'rgba(224, 74, 74, 0.12)',
|
|
16
17
|
success: '#FFE600',
|
|
17
18
|
danger: '#e04a4a',
|
|
@@ -24,10 +25,17 @@ export const COLORS = {
|
|
|
24
25
|
|
|
25
26
|
export const GLOW = {
|
|
26
27
|
text: '0 0 20px rgba(255, 230, 0, 0.18)',
|
|
28
|
+
brandMey:
|
|
29
|
+
'0 0 40px rgba(255,255,255,0.35), 0 0 80px rgba(255,255,255,0.15), 0 0 120px rgba(255,255,255,0.08)',
|
|
27
30
|
brandCult:
|
|
28
31
|
'0 0 40px rgba(255,230,0,0.35), 0 0 80px rgba(255,230,0,0.18), 0 0 120px rgba(255,230,0,0.08)',
|
|
29
32
|
} as const
|
|
30
33
|
|
|
34
|
+
export const GRADIENTS = {
|
|
35
|
+
launch: 'linear-gradient(135deg, #FFE600 0%, #FFF7A1 100%)',
|
|
36
|
+
launchBody: 'linear-gradient(180deg, #141003 0%, #4A3D00 100%)',
|
|
37
|
+
} as const
|
|
38
|
+
|
|
31
39
|
export const FONTS = {
|
|
32
40
|
heading: 'Rajdhani, sans-serif',
|
|
33
41
|
body: '"Space Grotesk", sans-serif',
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { cn } from '@meyvoid/core/cn'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
export type CultButtonVariant = 'accent' | 'premium' | 'ghost' | 'danger'
|
|
5
|
+
export type CultButtonSize = 'xs' | 'sm' | 'md' | 'lg'
|
|
6
|
+
|
|
7
|
+
export interface CultButtonProps {
|
|
8
|
+
children: ReactNode
|
|
9
|
+
variant?: CultButtonVariant
|
|
10
|
+
size?: CultButtonSize
|
|
11
|
+
className?: string
|
|
12
|
+
onClick?: () => void
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
type?: 'button' | 'submit' | 'reset'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Cult-themed button. (The generic void Button stays minimal/white;
|
|
18
|
+
* this is the themed superset — do NOT merge them.) */
|
|
19
|
+
export function CultButton({
|
|
20
|
+
children,
|
|
21
|
+
variant = 'accent',
|
|
22
|
+
size = 'md',
|
|
23
|
+
className,
|
|
24
|
+
onClick,
|
|
25
|
+
disabled,
|
|
26
|
+
type = 'button',
|
|
27
|
+
}: CultButtonProps) {
|
|
28
|
+
const variants: Record<CultButtonVariant, string> = {
|
|
29
|
+
accent: 'bg-accent hover:bg-accent/80 text-bg border-accent/50',
|
|
30
|
+
premium: 'bg-premium-500/20 hover:bg-premium-500/30 text-premium-400 border-premium-500/40',
|
|
31
|
+
ghost: 'bg-transparent hover:bg-surface/50 text-text-muted border-line/20',
|
|
32
|
+
danger: 'bg-red-900/30 hover:bg-red-900/50 text-red-400 border-red-700/40',
|
|
33
|
+
}
|
|
34
|
+
const sizes: Record<CultButtonSize, string> = {
|
|
35
|
+
xs: 'px-2 py-1 text-[10px]',
|
|
36
|
+
sm: 'px-3 py-1.5 text-xs',
|
|
37
|
+
md: 'px-4 py-2 text-sm',
|
|
38
|
+
lg: 'px-6 py-3 text-base',
|
|
39
|
+
}
|
|
40
|
+
return (
|
|
41
|
+
<button
|
|
42
|
+
type={type}
|
|
43
|
+
onClick={onClick}
|
|
44
|
+
disabled={disabled}
|
|
45
|
+
className={cn(
|
|
46
|
+
'rounded-lg border font-medium transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed',
|
|
47
|
+
variants[variant],
|
|
48
|
+
sizes[size],
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</button>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cn } from '@meyvoid/core/cn'
|
|
2
|
+
import type { Rarity } from '../game/types'
|
|
3
|
+
|
|
4
|
+
const RARITY_COLORS: Record<Rarity, string> = {
|
|
5
|
+
common: 'bg-surface text-text-muted border-surface',
|
|
6
|
+
uncommon: 'bg-green-900/40 text-green-400 border-green-700/40',
|
|
7
|
+
rare: 'bg-blue-900/40 text-blue-400 border-blue-700/40',
|
|
8
|
+
mythic: 'bg-premium-500/20 text-premium-400 border-premium-500/40',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Item rarity badge. (Quest-density tiers live in game/quests.ts — separate track.) */
|
|
12
|
+
export function RarityBadge({ rarity }: { rarity: string }) {
|
|
13
|
+
return (
|
|
14
|
+
<span className={cn('text-[10px] font-bold uppercase px-2 py-0.5 rounded border tracking-widest', RARITY_COLORS[rarity as Rarity] ?? RARITY_COLORS.common)}>
|
|
15
|
+
{rarity}
|
|
16
|
+
</span>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ComponentPropsWithoutRef, type CSSProperties, type FC } from "react";
|
|
2
2
|
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "@meyvoid/core/cn";
|
|
4
4
|
|
|
5
5
|
export interface AnimatedShinyTextProps extends ComponentPropsWithoutRef<"span"> {
|
|
6
6
|
shimmerWidth?: number;
|
|
@@ -14,7 +14,7 @@ export const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({
|
|
|
14
14
|
className,
|
|
15
15
|
shimmerWidth = 100,
|
|
16
16
|
gradientFrom = "transparent",
|
|
17
|
-
gradientVia = "rgba(
|
|
17
|
+
gradientVia = "rgba(255,230,0,0.65)",
|
|
18
18
|
gradientTo = "transparent",
|
|
19
19
|
...props
|
|
20
20
|
}) => {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cn } from '@meyvoid/core/cn'
|
|
2
|
+
import type { BadgeCategory, BadgeTier, PredictionResult } from '../game/types'
|
|
3
|
+
|
|
4
|
+
const TIER_COLORS: Record<BadgeTier, string> = {
|
|
5
|
+
bronze: 'border-amber-600 text-amber-600 bg-amber-600/10',
|
|
6
|
+
silver: 'border-slate-300 text-slate-300 bg-slate-300/10',
|
|
7
|
+
gold: 'border-yellow-400 text-yellow-400 bg-yellow-400/10',
|
|
8
|
+
platinum: 'border-zinc-200 text-zinc-200 bg-zinc-200/10',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TierBadge({ tier, size = 'sm' }: { tier: BadgeTier; size?: 'sm' | 'md' }) {
|
|
12
|
+
return (
|
|
13
|
+
<span className={cn(
|
|
14
|
+
'font-bold uppercase border rounded-full tracking-wider',
|
|
15
|
+
size === 'md' ? 'px-3 py-1 text-xs' : 'px-2 py-0.5 text-[10px]',
|
|
16
|
+
TIER_COLORS[tier],
|
|
17
|
+
)}>
|
|
18
|
+
{tier}
|
|
19
|
+
</span>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const BADGE_CATEGORY_ICONS: Record<BadgeCategory, string> = {
|
|
24
|
+
VIRTUE: '⬡', LOYALTY: '♥', COLLECTION: '◆', MARKET: '◈',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function BadgeCategoryChip({ category, active, onClick }: { category: BadgeCategory | 'ALL'; active: boolean; onClick: () => void }) {
|
|
28
|
+
const label = category === 'ALL' ? 'All' : category.charAt(0) + category.slice(1).toLowerCase()
|
|
29
|
+
const icon = category === 'ALL' ? '' : BADGE_CATEGORY_ICONS[category]
|
|
30
|
+
return (
|
|
31
|
+
<button onClick={onClick} className={cn('px-3 py-1 rounded-full text-xs font-medium transition-all border', active ? 'bg-accent/20 border-accent/30 text-accent' : 'border-line text-text-muted hover:text-text/70')}>
|
|
32
|
+
{icon} {label}
|
|
33
|
+
</button>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Prediction RESULT chips (WON/LOST/PENDING). Green/red are correct here —
|
|
38
|
+
* results, not outcomes. (YES/NO outcomes use text-yes/text-no per tokens.) */
|
|
39
|
+
export const RESULT_COLORS: Record<PredictionResult, string> = {
|
|
40
|
+
WON: 'text-green-400 bg-green-500/10',
|
|
41
|
+
LOST: 'text-red-400 bg-red-500/10',
|
|
42
|
+
PENDING: 'text-text-muted bg-line/20',
|
|
43
|
+
}
|
package/src/ui/card.tsx
CHANGED
|
@@ -1,102 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { cn } from '@meyvoid/core/cn'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
className,
|
|
6
|
-
size = "default",
|
|
7
|
-
...props
|
|
8
|
-
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
9
|
-
return (
|
|
10
|
-
<div
|
|
11
|
-
data-slot="card"
|
|
12
|
-
data-size={size}
|
|
13
|
-
className={cn(
|
|
14
|
-
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
|
15
|
-
className
|
|
16
|
-
)}
|
|
17
|
-
{...props}
|
|
18
|
-
/>
|
|
19
|
-
)
|
|
20
|
-
}
|
|
4
|
+
export type CardVariant = 'glow' | 'clarity' | 'depth' | 'reflect'
|
|
21
5
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
|
28
|
-
className
|
|
29
|
-
)}
|
|
30
|
-
{...props}
|
|
31
|
-
/>
|
|
32
|
-
)
|
|
6
|
+
export interface CardProps {
|
|
7
|
+
children: ReactNode
|
|
8
|
+
className?: string
|
|
9
|
+
variant?: CardVariant
|
|
10
|
+
onClick?: () => void
|
|
33
11
|
}
|
|
34
12
|
|
|
35
|
-
|
|
13
|
+
/** Canonical Cult card — glass shell + hover-variant classes (see theme.css card-*).
|
|
14
|
+
* Replaces the orphan shadcn card (wrong tokens, zero imports). */
|
|
15
|
+
export function Card({ children, className, variant, onClick }: CardProps) {
|
|
16
|
+
const variantClass = variant ? `card-${variant}` : ''
|
|
36
17
|
return (
|
|
37
18
|
<div
|
|
38
|
-
|
|
19
|
+
onClick={onClick}
|
|
39
20
|
className={cn(
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
'glass rounded-2xl p-4',
|
|
22
|
+
variantClass,
|
|
23
|
+
onClick && 'cursor-pointer',
|
|
24
|
+
className,
|
|
42
25
|
)}
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</div>
|
|
45
29
|
)
|
|
46
30
|
}
|
|
47
|
-
|
|
48
|
-
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
49
|
-
return (
|
|
50
|
-
<div
|
|
51
|
-
data-slot="card-description"
|
|
52
|
-
className={cn("text-sm text-muted-foreground", className)}
|
|
53
|
-
{...props}
|
|
54
|
-
/>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
59
|
-
return (
|
|
60
|
-
<div
|
|
61
|
-
data-slot="card-action"
|
|
62
|
-
className={cn(
|
|
63
|
-
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
64
|
-
className
|
|
65
|
-
)}
|
|
66
|
-
{...props}
|
|
67
|
-
/>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
72
|
-
return (
|
|
73
|
-
<div
|
|
74
|
-
data-slot="card-content"
|
|
75
|
-
className={cn("px-(--card-spacing)", className)}
|
|
76
|
-
{...props}
|
|
77
|
-
/>
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
82
|
-
return (
|
|
83
|
-
<div
|
|
84
|
-
data-slot="card-footer"
|
|
85
|
-
className={cn(
|
|
86
|
-
"flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
|
|
87
|
-
className
|
|
88
|
-
)}
|
|
89
|
-
{...props}
|
|
90
|
-
/>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export {
|
|
95
|
-
Card,
|
|
96
|
-
CardHeader,
|
|
97
|
-
CardFooter,
|
|
98
|
-
CardTitle,
|
|
99
|
-
CardAction,
|
|
100
|
-
CardDescription,
|
|
101
|
-
CardContent,
|
|
102
|
-
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { type ComponentPropsWithoutRef, type CSSProperties } from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { COLORS } from "../tokens";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@meyvoid/core/cn";
|
|
4
6
|
|
|
5
7
|
export interface ShimmerButtonProps extends ComponentPropsWithoutRef<"button"> {
|
|
6
8
|
shimmerColor?: string;
|
|
@@ -15,11 +17,11 @@ export interface ShimmerButtonProps extends ComponentPropsWithoutRef<"button"> {
|
|
|
15
17
|
export const ShimmerButton = React.forwardRef<HTMLButtonElement, ShimmerButtonProps>(
|
|
16
18
|
(
|
|
17
19
|
{
|
|
18
|
-
shimmerColor =
|
|
20
|
+
shimmerColor = COLORS.accent,
|
|
19
21
|
shimmerSize = "0.05em",
|
|
20
22
|
shimmerDuration = "3s",
|
|
21
23
|
borderRadius = "100px",
|
|
22
|
-
background = "rgba(
|
|
24
|
+
background = "rgba(20, 16, 3, 1)",
|
|
23
25
|
className,
|
|
24
26
|
children,
|
|
25
27
|
...props
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Eye, Droplets, Shield, Flame } from 'lucide-react'
|
|
2
|
+
import { Tooltip } from '@meyvoid/core'
|
|
3
|
+
import { CULT_META } from '../game/cults'
|
|
4
|
+
import type { VirtueName, VirtueTierLevel } from '../game/types'
|
|
5
|
+
|
|
6
|
+
/** The Cult virtue system as played. Colors come from CULT_META (each virtue
|
|
7
|
+
* is element-bound to its cult). Shape (tiers/thresholds) is faith-generic;
|
|
8
|
+
* content (labels/descs) is Cult — split if MeyGod needs the shape. */
|
|
9
|
+
export const VIRTUE_META: Record<VirtueName, { label: string; desc: string; color: string; tierDescs: Record<VirtueTierLevel, string>; icon: typeof Eye }> = {
|
|
10
|
+
clarity: { label: 'Clarity', desc: 'Info revealed before betting', color: CULT_META.driftless.color, icon: Eye, tierDescs: { 0: 'Base info only', 1: 'Trend direction arrows on quests', 2: 'Hero category W/L records visible', 3: 'Signal Reliability star rating' } },
|
|
11
|
+
humility: { label: 'Humility', desc: 'Grace through surrender', color: CULT_META.leviathan.color, icon: Droplets, tierDescs: { 0: 'Standard wagers', 1: 'Grace Pool contributions enabled', 2: 'Servant Wager bonuses active', 3: 'Absolution rite available' } },
|
|
12
|
+
endurance: { label: 'Endurance', desc: 'Steadfast through setbacks', color: CULT_META.masonry.color, icon: Shield, tierDescs: { 0: 'Full loss exposure', 1: '10% loss recovery', 2: 'Grounding Lock active', 3: 'Earthquake double payouts' } },
|
|
13
|
+
overcoming: { label: 'Overcoming', desc: 'Rise stronger from loss', color: CULT_META.recurrence.color, icon: Flame, tierDescs: { 0: 'No streak mechanics', 1: 'Ash Pool contributions', 2: 'Eternal Recurrence enabled', 3: 'Will-to-Power max multiplier' } },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const TIER_NAMES: Record<VirtueTierLevel, string> = { 0: 'Novice', 1: 'Initiate', 2: 'Adept', 3: 'Master' }
|
|
17
|
+
|
|
18
|
+
export function getVirtueTier(value: number): VirtueTierLevel {
|
|
19
|
+
if (value >= 15) return 3
|
|
20
|
+
if (value >= 10) return 2
|
|
21
|
+
if (value >= 5) return 1
|
|
22
|
+
return 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function VirtueBar({ virtue, value }: { virtue: VirtueName; value: number }) {
|
|
26
|
+
const meta = VIRTUE_META[virtue]
|
|
27
|
+
const tier = getVirtueTier(value)
|
|
28
|
+
const pct = (value / 20) * 100
|
|
29
|
+
const Icon = meta.icon
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="space-y-1.5">
|
|
33
|
+
<div className="flex justify-between items-center">
|
|
34
|
+
<div className="flex items-center gap-2">
|
|
35
|
+
<Icon size={14} style={{ color: meta.color }} />
|
|
36
|
+
<span className="text-sm text-text/80 font-medium">{meta.label}</span>
|
|
37
|
+
<span className="text-[11px] font-bold" style={{ color: meta.color }}>{TIER_NAMES[tier]}</span>
|
|
38
|
+
<span className="text-[10px] text-text-muted hidden sm:inline">{meta.desc}</span>
|
|
39
|
+
</div>
|
|
40
|
+
<span className="font-mono text-sm font-bold" style={{ color: meta.color }}>{value}</span>
|
|
41
|
+
</div>
|
|
42
|
+
<Tooltip
|
|
43
|
+
content={
|
|
44
|
+
<>
|
|
45
|
+
<p className="text-text/80 font-medium">Current: {TIER_NAMES[tier]}</p>
|
|
46
|
+
<p className="text-text/60">{meta.tierDescs[tier]}</p>
|
|
47
|
+
{tier < 3 && (
|
|
48
|
+
<p className="text-text-muted mt-1">Next: {TIER_NAMES[(tier + 1) as VirtueTierLevel]} — {meta.tierDescs[(tier + 1) as VirtueTierLevel]}</p>
|
|
49
|
+
)}
|
|
50
|
+
</>
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
<div className="w-full bg-surface rounded-full h-3 overflow-hidden">
|
|
54
|
+
<div
|
|
55
|
+
className="h-full rounded-full virtue-bar-fill"
|
|
56
|
+
style={{
|
|
57
|
+
width: `${pct}%`,
|
|
58
|
+
background: `linear-gradient(90deg, ${meta.color}, color-mix(in srgb, ${meta.color} 50%, #ffffff), ${meta.color})`,
|
|
59
|
+
backgroundSize: '200% 100%',
|
|
60
|
+
} as React.CSSProperties}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</Tooltip>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -5,7 +5,7 @@ import { useEffect } from 'react'
|
|
|
5
5
|
import { GrainVeil } from '@meyvoid/core/backgrounds'
|
|
6
6
|
import { BG_STYLES } from './bgStyles'
|
|
7
7
|
import { parseBgQuery, useUnveilBg } from './bg'
|
|
8
|
-
import { AshCanvas, BreathVignette, RuneCanvas, VeilFog } from './effects'
|
|
8
|
+
import { AshCanvas, BreathVignette, RuneCanvas, VeilFog, WallCanvas } from './effects'
|
|
9
9
|
|
|
10
10
|
export function LaunchStage() {
|
|
11
11
|
const { effects, density, speed, opacity, grain, setMany } = useUnveilBg()
|
|
@@ -20,8 +20,9 @@ export function LaunchStage() {
|
|
|
20
20
|
const fx = { density, speed, opacity }
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<div className="pointer-events-none
|
|
23
|
+
<div className="pointer-events-none absolute inset-0 z-0 overflow-hidden" aria-hidden="true">
|
|
24
24
|
<style>{BG_STYLES}</style>
|
|
25
|
+
{effects.includes('wall') && <WallCanvas opacity={opacity} />}
|
|
25
26
|
{effects.includes('veil') && <VeilFog opacity={opacity} />}
|
|
26
27
|
{effects.includes('ash') && <AshCanvas {...fx} />}
|
|
27
28
|
{effects.includes('runes') && <RuneCanvas {...fx} />}
|