@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.
Files changed (43) hide show
  1. package/package.json +3 -6
  2. package/src/brand/BrandName.tsx +4 -2
  3. package/src/brand/LaunchButton.tsx +5 -3
  4. package/src/brand/Logo.tsx +25 -6
  5. package/src/brand/PyramidName.tsx +10 -0
  6. package/src/brand/Sigil.tsx +10 -5
  7. package/src/brand/TokenIcons.tsx +12 -6
  8. package/src/brand.ts +4 -1
  9. package/src/components/CultCountdown.tsx +89 -14
  10. package/src/game/categories.ts +23 -0
  11. package/src/game/cults.ts +88 -0
  12. package/src/game/quests.ts +62 -0
  13. package/src/game/types.ts +27 -0
  14. package/src/index.ts +64 -21
  15. package/src/product.ts +17 -0
  16. package/src/styles/theme.css +97 -6
  17. package/src/tokens.ts +10 -2
  18. package/src/ui/CultButton.tsx +55 -0
  19. package/src/ui/RarityBadge.tsx +18 -0
  20. package/src/ui/animated-shiny-text.tsx +2 -2
  21. package/src/ui/badges.tsx +43 -0
  22. package/src/ui/card.tsx +20 -92
  23. package/src/ui/shimmer-button.tsx +5 -3
  24. package/src/ui/virtues.tsx +66 -0
  25. package/src/unveil/LaunchStage.tsx +3 -2
  26. package/src/unveil/bg.ts +20 -11
  27. package/src/unveil/bgStyles.ts +0 -1
  28. package/src/unveil/blocks.tsx +51 -14
  29. package/src/unveil/effects.tsx +182 -86
  30. package/src/unveil.ts +2 -21
  31. package/src/auth/Gate.tsx +0 -14
  32. package/src/auth/types.ts +0 -12
  33. package/src/brand/GodEmperor.tsx +0 -8
  34. package/src/cn.ts +0 -6
  35. package/src/lib/images.ts +0 -10
  36. package/src/lib/money.ts +0 -12
  37. package/src/lib/store.ts +0 -19
  38. package/src/shell/types.ts +0 -13
  39. package/src/supabase/browser.ts +0 -103
  40. package/src/supabase/callback.ts +0 -15
  41. package/src/supabase/proxy.ts +0 -36
  42. package/src/supabase/server.ts +0 -31
  43. package/src/ui/Callout.tsx +0 -33
package/src/index.ts CHANGED
@@ -1,22 +1,82 @@
1
- export { cn } from './cn'
2
- export { COLORS, GLOW, FONTS } from './tokens'
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
@@ -323,12 +323,24 @@ h2 {
323
323
  color: var(--color-accent);
324
324
  }
325
325
 
326
- /* ── Light glow behind Unveil, same treatment as MeyCult wordmark ── */
326
+ /* ── Yellow glow behind Unveil, same treatment as MeyCult wordmark ── */
327
327
  .unveil-glow {
328
328
  text-shadow:
329
- 0 0 40px rgba(255, 255, 255, 0.35),
330
- 0 0 80px rgba(255, 255, 255, 0.15),
331
- 0 0 120px rgba(255, 255, 255, 0.08);
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, #2a2003 0%, #171402 100%);
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.65),
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: '#0B0A02',
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.08)',
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 "../cn";
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(124,255,46,0.65)",
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 * as React from "react"
2
- import { cn } from "../cn"
1
+ import { cn } from '@meyvoid/core/cn'
2
+ import type { ReactNode } from 'react'
3
3
 
4
- function Card({
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
- function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
23
- return (
24
- <div
25
- data-slot="card-header"
26
- className={cn(
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
- function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
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
- data-slot="card-title"
19
+ onClick={onClick}
39
20
  className={cn(
40
- "font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
41
- className
21
+ 'glass rounded-2xl p-4',
22
+ variantClass,
23
+ onClick && 'cursor-pointer',
24
+ className,
42
25
  )}
43
- {...props}
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 { cn } from "../cn";
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 = "#b6ff7a",
20
+ shimmerColor = COLORS.accent,
19
21
  shimmerSize = "0.05em",
20
22
  shimmerDuration = "3s",
21
23
  borderRadius = "100px",
22
- background = "rgba(4, 16, 7, 1)",
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 fixed inset-0 z-0 overflow-hidden" aria-hidden="true">
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} />}