diff --git a/site/app/globals.css b/site/app/globals.css index 2d327f4..b9699f9 100644 --- a/site/app/globals.css +++ b/site/app/globals.css @@ -78,3 +78,37 @@ body { linear-gradient(-45deg, rgba(212, 175, 55, 0.04) 25%, transparent 25%); background-size: 22px 22px; } + +/* Gentle float for the hero logo. */ +@keyframes float-y { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-12px); } +} +.float-y { + animation: float-y 5.5s ease-in-out infinite; +} + +/* Soft gold halo behind the hero logo. */ +.gold-halo { + background: radial-gradient(circle, rgba(212, 175, 55, 0.28), transparent 62%); + filter: blur(8px); +} + +/* Card-suit accent for hero/section glyphs. */ +.suit { + color: var(--gold); + opacity: 0.16; + user-select: none; + line-height: 1; +} + +/* Thin gold hairline divider. */ +.rule-gold { + height: 1px; + background: linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.5), transparent); +} + +@media (prefers-reduced-motion: reduce) { + .float-y { animation: none; } + html { scroll-behavior: auto; } +} diff --git a/site/app/page.tsx b/site/app/page.tsx index 2fba39e..4a64cca 100644 --- a/site/app/page.tsx +++ b/site/app/page.tsx @@ -2,6 +2,7 @@ import { Users, Bot, Trophy, Gift, MessageCircle, Globe, ShieldCheck, Zap, Crown, Star, } from "lucide-react"; import { DownloadButtons } from "@/components/DownloadButtons"; +import { Logo } from "@/components/Logo"; import { BRAND } from "@/lib/site"; const FEATURES = [ @@ -29,8 +30,22 @@ export default function Home() { return ( <> {/* Hero */} -
+
+ {/* decorative card suits floating in the backdrop */} + + + + +
+ {/* card-fan brand mark */} +
+
+
+ +
+
+ بازی حکمِ ایرانی، حرفه‌ای‌تر از همیشه @@ -48,7 +63,7 @@ export default function Home() {
{STATS.map((s) => ( -
+
{s.label}
{s.value}
@@ -66,6 +81,7 @@ export default function Home() {

همهٔ چیزی که یک بازی حکم بی‌نقص لازم دارد، در یک اپ.

+
{FEATURES.map((f) => (
@@ -97,6 +113,9 @@ export default function Home() { {/* Final CTA */}
+
+ +

همین حالا حکم را شروع کن

diff --git a/site/components/Logo.tsx b/site/components/Logo.tsx index cfabc6d..c0bd47d 100644 --- a/site/components/Logo.tsx +++ b/site/components/Logo.tsx @@ -1,24 +1,67 @@ -export function Logo({ size = 36 }: { size?: number }) { +/** + * Brand mark — the app's card-fan icon (mirrors public/icon.svg): three gold-edged + * playing cards fanned out, a spade on the face card. Scales cleanly from the nav + * (≈34px) to the hero (≈160px). `glow` adds a soft gold halo for hero use. + */ +export function Logo({ size = 36, glow = false }: { size?: number; glow?: boolean }) { return ( - - - - و - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/site/public/icon.svg b/site/public/icon.svg index 2208281..02f869d 100644 --- a/site/public/icon.svg +++ b/site/public/icon.svg @@ -1,4 +1,45 @@ - - - و + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/sound.ts b/src/lib/sound.ts index 3ff84be..6d8ed6f 100644 --- a/src/lib/sound.ts +++ b/src/lib/sound.ts @@ -38,7 +38,7 @@ class SoundManager { sfxEnabled = loadBool(LS_SFX); musicEnabled = loadBool(LS_MUSIC); musicTrack: MusicTrack = - (typeof window !== "undefined" && (localStorage.getItem(LS_TRACK) as MusicTrack)) || "playful"; + (typeof window !== "undefined" && (localStorage.getItem(LS_TRACK) as MusicTrack)) || "santoor"; /** Must be called from a user gesture to unlock audio. */ init() { @@ -198,11 +198,22 @@ class SoundManager { // • playful — bouncy major-pentatonic staccato loop (UNO-like). private TRACKS: Record< MusicTrack, - { notes: number[]; gap: number; type: OscillatorType; attack: number; dur: number; peak: number; fifth: boolean } + { + notes: number[]; gap: number; type: OscillatorType; attack: number; dur: number; + peak: number; fifth: boolean; + shimmer?: boolean; // bright metallic overtones — santoor pluck character + bassHz?: number; // soft sustained tonic drone underneath + } > = { santoor: { - notes: [293.66, 311.13, 392, 440, 466.16, 392, 311.13, 293.66], - gap: 900, type: "sine", attack: 0.3, dur: 1.6, peak: 0.5, fifth: true, + // Dastgah-e Shur on D — a calm phrase that rises then settles, looping + // seamlessly. Plucked (fast attack, long decay) with metallic shimmer. + notes: [ + 293.66, 349.23, 392.0, 440.0, 392.0, 349.23, 311.13, 293.66, + 349.23, 392.0, 466.16, 440.0, 392.0, 349.23, 311.13, 293.66, + ], + gap: 470, type: "triangle", attack: 0.004, dur: 1.15, peak: 0.4, + fifth: false, shimmer: true, bassHz: 73.42, // D2 drone }, playful: { notes: [523.25, 659.25, 784, 659.25, 587.33, 698.46, 880, 698.46, 587.33, 523.25], @@ -218,23 +229,35 @@ class SoundManager { if (!this.ctx || !this.musicGain) return; const cfg = this.TRACKS[this.musicTrack]; const now = this.ctx.currentTime; - const freq = cfg.notes[this.step % cfg.notes.length]; + const idx = this.step % cfg.notes.length; + const freq = cfg.notes[idx]; this.step++; - const voice = (f: number, scale = 1) => { + const voice = (f: number, scale = 1, type = cfg.type, attack = cfg.attack, dur = cfg.dur) => { const osc = this.ctx!.createOscillator(); const g = this.ctx!.createGain(); - osc.type = cfg.type; + osc.type = type; osc.frequency.value = f; g.gain.setValueAtTime(0.0001, now); - g.gain.linearRampToValueAtTime(cfg.peak * scale, now + cfg.attack); - g.gain.exponentialRampToValueAtTime(0.0001, now + cfg.dur); + g.gain.linearRampToValueAtTime(cfg.peak * scale, now + attack); + g.gain.exponentialRampToValueAtTime(0.0001, now + dur); osc.connect(g); g.connect(this.musicGain!); osc.start(now); - osc.stop(now + cfg.dur + 0.05); + osc.stop(now + dur + 0.05); }; voice(freq); if (cfg.fifth) voice(freq * 1.5, 0.45); // soft fifth above + if (cfg.shimmer) { + // Metallic santoor overtones: a brighter octave + a faint third partial, + // both with quick decay so the pluck shimmers then fades. + voice(freq * 2, 0.16, "sine", 0.003, cfg.dur * 0.55); + voice(freq * 3, 0.06, "sine", 0.003, cfg.dur * 0.4); + } + // Soft sustained tonic drone re-struck once per phrase half. + if (cfg.bassHz && idx % (cfg.notes.length / 2) === 0) { + const span = (cfg.gap / 1000) * (cfg.notes.length / 2); + voice(cfg.bassHz, 0.5, "sine", 0.25, span * 0.95); + } }; tick(); this.musicTimer = setInterval(tick, this.TRACKS[this.musicTrack].gap);