// brand.jsx — OKxProject / InstaSatış brand tokens + shared primitives
// Expose to window at bottom so other babel files can read them.
const I18N = {
tr: {
nav_explore: 'Keşfet', nav_how: 'Nasıl çalışır', nav_about: 'Hakkımızda', nav_help: 'Yardım',
nav_sell: 'İlan Ver',
hero_kicker: 'Aged + Konsept Instagram Pazaryeri',
hero_title: 'Doğru hesap, doğru fiyat.',
hero_sub: 'Yaşlandırılmış ve nişe özel Instagram hesaplarını emin ellerde satın al. SSL koruması, iade garantisi.',
hero_cta: 'İlanları Gör', hero_cta2: 'İletişime Geç',
stat_listings: 'aktif ilan', stat_sold: 'teslim edilen hesap', stat_uptime: 'güvenli işlem',
filter_title: 'Filtrele', filter_age: 'Hesap Yaşı', filter_niche: 'Kategori', filter_followers: 'Takipçi',
filter_price: 'Fiyat', filter_reset: 'Sıfırla', filter_apply: 'Uygula',
age_any: 'Tümü', age_1: '1+ Yıl', age_3: '3+ Yıl', age_5: '5+ Yıl',
sort_new: 'En Yeni', sort_price_low: 'Fiyat ↑', sort_price_high: 'Fiyat ↓', sort_followers: 'Takipçi ↓', sort_age: 'Yaş ↓',
card_followers: 'takipçi', card_age: 'yaş', card_age_years: 'yıl', card_age_months: 'ay',
card_buy: 'Şimdi Al', card_view: 'İncele', card_saved: 'Kaydedildi', card_save: 'Kaydet',
detail_overview: 'Genel Bakış', detail_metrics: 'Metrikler', detail_content: 'İçerik Örnekleri', detail_seller: 'Satıcı',
detail_buy_now: 'Şimdi Al', detail_add_fav: 'Favorilere Ekle', detail_report: 'Bildir',
trust_ssl: 'SSL Koruması', trust_card: 'Kredi Kartı', trust_iban: 'IBAN / Havale', trust_refund: '7 gün iade', trust_verified: 'Doğrulanmış',
pay_title: 'Ödeme', pay_sub: 'İki güvenli yöntemden birini seç.', pay_method_card: 'Kredi / Banka Kartı', pay_method_iban: 'IBAN · Havale/EFT', pay_card_number: 'Kart Numarası', pay_card_name: 'Kart Üzerindeki İsim', pay_card_exp: 'Son Kullanma', pay_card_cvv: 'CVV', pay_iban_owner: 'Hesap Sahibi', pay_iban_num: 'IBAN', pay_iban_note: 'Havale açıklamasına sipariş numaranı yaz. Onay 10 dk içinde gelir.', pay_order_summary: 'Sipariş Özeti', pay_subtotal: 'Ara Toplam', pay_fee: 'Hizmet Bedeli (%2)', pay_total: 'Toplam', pay_cta_card: 'Kartla Öde', pay_cta_iban: 'IBAN Bilgilerini Göster', pay_installment: 'Taksit seçenekleri', pay_3d: '3D Secure ile korunur',
auth_welcome: 'Hoş geldin', auth_sub: 'Pazaryerine giriş yap veya hesap oluştur.',
auth_email: 'E-posta', auth_pass: 'Şifre', auth_login: 'Giriş Yap', auth_signup: 'Kayıt Ol',
auth_or: 'ya da', auth_google: 'Google ile devam et', auth_tos: 'Devam ederek koşulları kabul edersin',
auth_tab_login: 'Giriş', auth_tab_signup: 'Kayıt',
dash_hello: 'Merhaba', dash_fav: 'Favorilerim', dash_watch: 'Takibimdeki', dash_offers: 'Tekliflerim', dash_purchases: 'Satın Aldıklarım',
empty_fav: 'Henüz favori eklemedin.',
price_ttl: 'Fiyat',
niche_fashion: 'Moda', niche_food: 'Yemek', niche_travel: 'Seyahat', niche_meme: 'Mizah', niche_sport: 'Spor', niche_tech: 'Teknoloji', niche_art: 'Sanat', niche_lifestyle: 'Yaşam', niche_music: 'Müzik', niche_beauty: 'Güzellik', niche_fitness: 'Fitness', niche_auto: 'Otomobil',
new_badge: 'YENİ', hot_badge: 'POPÜLER', rare_badge: 'NADİR',
search_ph: 'Kategori, niş veya @kullaniciadi ara…',
},
en: {
nav_explore: 'Explore', nav_how: 'How it works', nav_about: 'About', nav_help: 'Help',
nav_sell: 'Sell Account',
hero_kicker: 'Aged + Niche Instagram Marketplace',
hero_title: 'The right account, at the right price.',
hero_sub: 'Buy aged and niche Instagram accounts in trusted hands. SSL protection, refund policy.',
hero_cta: 'See Listings', hero_cta2: 'Contact Us',
stat_listings: 'active listings', stat_sold: 'accounts delivered', stat_uptime: 'safe transactions',
filter_title: 'Filter', filter_age: 'Account Age', filter_niche: 'Niche', filter_followers: 'Followers',
filter_price: 'Price', filter_reset: 'Reset', filter_apply: 'Apply',
age_any: 'Any', age_1: '1+ Year', age_3: '3+ Years', age_5: '5+ Years',
sort_new: 'Newest', sort_price_low: 'Price ↑', sort_price_high: 'Price ↓', sort_followers: 'Followers ↓', sort_age: 'Age ↓',
card_followers: 'followers', card_age: 'age', card_age_years: 'yrs', card_age_months: 'mo',
card_buy: 'Buy Now', card_view: 'View', card_saved: 'Saved', card_save: 'Save',
detail_overview: 'Overview', detail_metrics: 'Metrics', detail_content: 'Content Samples', detail_seller: 'Seller',
detail_buy_now: 'Buy Now', detail_add_fav: 'Add to favorites', detail_report: 'Report',
trust_ssl: 'SSL Protected', trust_card: 'Credit Card', trust_iban: 'IBAN · Wire', trust_refund: '7-day refund', trust_verified: 'Verified',
pay_title: 'Checkout', pay_sub: 'Choose one of two secure methods.', pay_method_card: 'Credit / Debit Card', pay_method_iban: 'IBAN · Wire Transfer', pay_card_number: 'Card Number', pay_card_name: 'Name on Card', pay_card_exp: 'Expiry', pay_card_cvv: 'CVV', pay_iban_owner: 'Account Owner', pay_iban_num: 'IBAN', pay_iban_note: 'Write your order ID in the transfer note. Confirmed in ~10 min.', pay_order_summary: 'Order Summary', pay_subtotal: 'Subtotal', pay_fee: 'Service Fee (2%)', pay_total: 'Total', pay_cta_card: 'Pay by Card', pay_cta_iban: 'Show IBAN Details', pay_installment: 'Installment options', pay_3d: 'Protected by 3D Secure',
escrow_word: 'Escrow', auth_welcome: 'Welcome', auth_sub: 'Sign in to the marketplace or create an account.',
auth_email: 'Email', auth_pass: 'Password', auth_login: 'Log In', auth_signup: 'Sign Up',
auth_or: 'or', auth_google: 'Continue with Google', auth_tos: 'By continuing you accept the terms',
auth_tab_login: 'Login', auth_tab_signup: 'Signup',
dash_hello: 'Hello', dash_fav: 'Favorites', dash_watch: 'Watching', dash_offers: 'My Offers', dash_purchases: 'Purchases',
empty_fav: 'No favorites yet.',
price_ttl: 'Price',
niche_fashion: 'Fashion', niche_food: 'Food', niche_travel: 'Travel', niche_meme: 'Humor', niche_sport: 'Sport', niche_tech: 'Tech', niche_art: 'Art', niche_lifestyle: 'Lifestyle', niche_music: 'Music', niche_beauty: 'Beauty', niche_fitness: 'Fitness', niche_auto: 'Auto',
new_badge: 'NEW', hot_badge: 'HOT', rare_badge: 'RARE',
search_ph: 'Search by niche, category or @handle…',
},
};
// Color palettes — each palette defines the gradient accent stops + neutrals
// Work in oklch for harmony. Keep chroma roughly matched across hues.
const PALETTES = {
sunset: {
name: 'Sunset',
// warm coral → magenta → violet — this is the default, instagram-adjacent
g1: 'oklch(0.74 0.19 35)', // coral
g2: 'oklch(0.66 0.25 350)', // pink-magenta
g3: 'oklch(0.58 0.22 300)', // violet
accent: 'oklch(0.66 0.22 15)',
accentInk: '#ffffff',
},
citrus: {
name: 'Citrus',
g1: 'oklch(0.86 0.18 95)', // lemon
g2: 'oklch(0.75 0.22 55)', // orange
g3: 'oklch(0.65 0.22 25)', // red
accent: 'oklch(0.70 0.22 45)',
accentInk: '#1a1308',
},
ocean: {
name: 'Ocean',
g1: 'oklch(0.78 0.15 210)',
g2: 'oklch(0.64 0.18 245)',
g3: 'oklch(0.52 0.22 285)',
accent: 'oklch(0.62 0.20 240)',
accentInk: '#ffffff',
},
matcha: {
name: 'Matcha',
g1: 'oklch(0.85 0.14 120)',
g2: 'oklch(0.70 0.18 155)',
g3: 'oklch(0.55 0.15 185)',
accent: 'oklch(0.62 0.16 160)',
accentInk: '#06120c',
},
};
// Derived CSS variable map for a theme: dark flag + palette + density
function themeVars({ dark, palette, density }) {
const p = PALETTES[palette] || PALETTES.sunset;
const base = dark
? {
'--bg': 'oklch(0.16 0.01 280)',
'--bg-2': 'oklch(0.20 0.015 280)',
'--bg-3': 'oklch(0.24 0.018 280)',
'--ink': 'oklch(0.97 0.005 280)',
'--ink-2': 'oklch(0.82 0.01 280)',
'--ink-3': 'oklch(0.62 0.01 280)',
'--line': 'oklch(0.32 0.01 280 / 0.7)',
'--line-2': 'oklch(0.28 0.01 280 / 0.5)',
'--card': 'oklch(0.20 0.015 280)',
'--card-2': 'oklch(0.24 0.018 280)',
'--chip': 'oklch(0.26 0.02 280)',
'--hover': 'oklch(0.26 0.02 280)',
'--success': 'oklch(0.75 0.17 155)',
'--warn': 'oklch(0.78 0.17 70)',
'--danger': 'oklch(0.68 0.22 25)',
}
: {
'--bg': 'oklch(0.99 0.003 80)',
'--bg-2': 'oklch(0.97 0.004 80)',
'--bg-3': 'oklch(0.94 0.006 80)',
'--ink': 'oklch(0.18 0.01 280)',
'--ink-2': 'oklch(0.38 0.01 280)',
'--ink-3': 'oklch(0.55 0.01 280)',
'--line': 'oklch(0.90 0.005 280)',
'--line-2': 'oklch(0.94 0.004 280)',
'--card': '#ffffff',
'--card-2': 'oklch(0.98 0.003 80)',
'--chip': 'oklch(0.96 0.005 80)',
'--hover': 'oklch(0.96 0.005 80)',
'--success': 'oklch(0.55 0.16 155)',
'--warn': 'oklch(0.65 0.18 70)',
'--danger': 'oklch(0.58 0.22 25)',
};
const d = density === 'compact' ? { '--gap': '10px', '--card-pad': '14px', '--row-h': '36px' }
: density === 'comfy' ? { '--gap': '20px', '--card-pad': '22px', '--row-h': '44px' }
: { '--gap': '14px', '--card-pad': '18px', '--row-h': '40px' };
return {
...base, ...d,
'--g1': p.g1, '--g2': p.g2, '--g3': p.g3,
'--accent': p.accent, '--accent-ink': p.accentInk,
'--grad': `linear-gradient(135deg, ${p.g1} 0%, ${p.g2} 50%, ${p.g3} 100%)`,
'--grad-soft': `linear-gradient(135deg, ${p.g1} 0%, ${p.g2} 100%)`,
'--paletteName': p.name,
};
}
// ─────────────────────────────────────────────────────────────
// Logo — OKxProject's InstaSatış wordmark. Original geometric mark:
// a stacked "OK" monogram inside a rounded squircle with a small node
// in the bottom-right representing "x" / transaction. Not a copy of
// any existing logo.
// ─────────────────────────────────────────────────────────────
function Logo({ size = 32, showWord = true, wordColor = 'currentColor' }) {
return (
is
{showWord && (
instasatış
)}
);
}
// ─────────────────────────────────────────────────────────────
// Icons — small set, stroke-based
// ─────────────────────────────────────────────────────────────
function Icon({ name, size = 18, color = 'currentColor', strokeWidth = 1.75 }) {
const s = size;
const common = { width: s, height: s, viewBox: '0 0 24 24', fill: 'none', stroke: color, strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round' };
switch (name) {
case 'search': return ( );
case 'heart': return ( );
case 'heart-fill': return ( );
case 'shield': return ( );
case 'bolt': return ( );
case 'users': return ( );
case 'calendar': return ( );
case 'tag': return ( );
case 'check': return ( );
case 'lock': return ( );
case 'mail': return ( );
case 'sparkle': return ( );
case 'flame': return ( );
case 'verified': return ( );
case 'chevron-right': return ( );
case 'chevron-left': return ( );
case 'chevron-down': return ( );
case 'chevron-up': return ( );
case 'info': return ( );
case 'arrow-right': return ( );
case 'filter': return ( );
case 'grid': return ( );
case 'globe': return ( );
case 'bell': return ( );
case 'x': return ( );
case 'plus': return ( );
case 'menu': return ( );
case 'star': return ( );
case 'upload': return ( );
case 'eye': return ( );
case 'flag': return ( );
case 'share': return ( );
case 'chat': return ( );
case 'send': return ( );
case 'arrow-right-up': return ( );
default: return null;
}
}
// ─────────────────────────────────────────────────────────────
// Global styles — injected once per document
// ─────────────────────────────────────────────────────────────
function injectGlobalStyles() {
if (document.getElementById('is-global-styles')) return;
const s = document.createElement('style');
s.id = 'is-global-styles';
s.textContent = `
:root { --font-display: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
--font-body: 'Inter', ui-sans-serif, system-ui, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, monospace; }
* { box-sizing: border-box; }
.is-surface { font-family: var(--font-body); color: var(--ink); background: var(--bg); }
.is-btn { font-family: var(--font-body); font-weight: 600; border: 0; cursor: pointer;
border-radius: 999px; padding: 10px 18px; font-size: 14px;
display: inline-flex; align-items: center; gap: 8px;
transition: transform .12s, box-shadow .12s, background .12s;
white-space: nowrap; }
.is-btn:hover { transform: translateY(-1px); }
.is-btn-primary { background: var(--grad); color: #fff;
box-shadow: 0 6px 20px -6px oklch(0.66 0.25 350 / 0.5); }
.is-btn-ghost { background: var(--chip); color: var(--ink); }
.is-btn-outline { background: transparent; color: var(--ink);
box-shadow: inset 0 0 0 1px var(--line); }
.is-chip { display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px; border-radius: 999px;
background: var(--chip); color: var(--ink-2);
font-size: 12px; font-weight: 500; font-family: var(--font-body); }
.is-chip-accent { background: var(--grad); color: #fff; }
.is-card { background: var(--card); border-radius: 18px;
box-shadow: 0 0 0 1px var(--line-2);
overflow: hidden; transition: transform .15s, box-shadow .15s; }
.is-card:hover { box-shadow: 0 0 0 1px var(--line), 0 12px 30px -12px rgba(0,0,0,0.12); }
.is-kicker { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.14em;
text-transform: uppercase; color: var(--ink-3); }
.is-h1 { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.03em;
line-height: 1.02; color: var(--ink); }
.is-h2 { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.02em;
line-height: 1.05; color: var(--ink); }
.is-mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.is-scroll-x { overflow-x: auto; scrollbar-width: none; }
.is-scroll-x::-webkit-scrollbar { display: none; }
/* Subtle striped placeholder for imagery */
.is-ph { background-image: repeating-linear-gradient(45deg,
var(--bg-3) 0 8px, var(--bg-2) 8px 16px);
color: var(--ink-3); font-family: var(--font-mono); font-size: 11px;
display: grid; place-items: center; }
.is-grad-text { background: var(--grad); -webkit-background-clip: text;
background-clip: text; color: transparent; }
.is-ring { border-radius: 50%; background: var(--grad); padding: 2px; }
.is-ring > div { border-radius: 50%; background: var(--card); width: 100%; height: 100%;
overflow: hidden; display: grid; place-items: center; }
input.is-input, textarea.is-input {
appearance: none; background: var(--card-2); border: 1px solid var(--line);
border-radius: 12px; padding: 12px 14px; font-size: 14px; color: var(--ink);
font-family: var(--font-body); width: 100%; outline: none;
transition: border-color .12s, box-shadow .12s;
}
input.is-input:focus, textarea.is-input:focus {
border-color: transparent;
box-shadow: 0 0 0 2px var(--accent);
}
`;
document.head.appendChild(s);
}
// ─────────────────────────────────────────────────────────────
// Mock listings — realistic shape; usernames are invented, not scraped
// ─────────────────────────────────────────────────────────────
const NICHE_COLORS = {
fashion: ['#ff6b9d', '#c94b8d'],
food: ['#ff9558', '#e25822'],
travel: ['#4ec0ca', '#2a7a8c'],
meme: ['#ffce55', '#e1a11c'],
sport: ['#4cd964', '#1a8b3c'],
tech: ['#6c7cff', '#3a47c8'],
art: ['#c56bff', '#8a3cd8'],
lifestyle: ['#f6a6c1', '#d06a8a'],
music: ['#ff5a8a', '#b02a5e'],
beauty: ['#ff8fb0', '#d45a80'],
fitness: ['#66d0a5', '#2e8a6a'],
auto: ['#8793a6', '#4a5365'],
};
let LISTINGS = [
// Travel — 3 listings
{ id: 'l1', handle: '@citytrails', niche: 'travel', followers: 128400, age: 62, price: 18500, er: 4.2, verified: true, badge: 'hot', country: 'TR', postsStyle: 'travel' },
{ id: 'l1b', handle: '@nomad.notes', niche: 'travel', followers: 76200, age: 41, price: 12400, er: 5.6, verified: false, badge: null, country: 'TR', postsStyle: 'travel' },
{ id: 'l1c', handle: '@aegean.diary', niche: 'travel', followers: 195300, age: 73, price: 28900, er: 3.9, verified: true, badge: 'rare', country: 'TR', postsStyle: 'travel' },
// Fashion — 3
{ id: 'l2', handle: '@velvet.looks', niche: 'fashion', followers: 87200, age: 48, price: 14900, er: 5.1, verified: false, badge: null, country: 'TR', postsStyle: 'fashion' },
{ id: 'l2b', handle: '@minimal.thread', niche: 'fashion', followers: 134500, age: 56, price: 21800, er: 4.4, verified: true, badge: 'hot', country: 'TR', postsStyle: 'fashion' },
{ id: 'l2c', handle: '@runway.daily', niche: 'fashion', followers: 58900, age: 33, price: 10200, er: 6.2, verified: false, badge: 'new', country: 'TR', postsStyle: 'fashion' },
// Food — 3
{ id: 'l3', handle: '@kitchenlab.tr', niche: 'food', followers: 54300, age: 36, price: 9800, er: 6.4, verified: false, badge: 'new', country: 'TR', postsStyle: 'food' },
{ id: 'l3b', handle: '@bistro.notes', niche: 'food', followers: 112800, age: 58, price: 17600, er: 5.0, verified: true, badge: null, country: 'TR', postsStyle: 'food' },
{ id: 'l3c', handle: '@street.bites', niche: 'food', followers: 89400, age: 44, price: 13900, er: 5.7, verified: false, badge: null, country: 'TR', postsStyle: 'food' },
// Tech — 2
{ id: 'l4', handle: '@byte.daily', niche: 'tech', followers: 212800, age: 71, price: 32500, er: 3.8, verified: true, badge: 'rare', country: 'US', postsStyle: 'tech' },
{ id: 'l4b', handle: '@gadget.lab', niche: 'tech', followers: 73600, age: 39, price: 11800, er: 4.5, verified: false, badge: 'new', country: 'TR', postsStyle: 'tech' },
// Sport — 2
{ id: 'l5', handle: '@pitchside', niche: 'sport', followers: 43100, age: 52, price: 7200, er: 5.8, verified: false, badge: null, country: 'TR', postsStyle: 'sport' },
{ id: 'l5b', handle: '@matchday.tr', niche: 'sport', followers: 168400, age: 64, price: 24600, er: 4.7, verified: true, badge: 'hot', country: 'TR', postsStyle: 'sport' },
// Art — 2
{ id: 'l6', handle: '@soft.studio', niche: 'art', followers: 96500, age: 44, price: 16200, er: 4.9, verified: false, badge: null, country: 'TR', postsStyle: 'art' },
{ id: 'l6b', handle: '@inkwell.daily', niche: 'art', followers: 47800, age: 31, price: 8400, er: 6.0, verified: false, badge: 'new', country: 'TR', postsStyle: 'art' },
// Meme — 2
{ id: 'l7', handle: '@gigglesnap', niche: 'meme', followers: 318700, age: 55, price: 24800, er: 7.9, verified: false, badge: 'hot', country: 'TR', postsStyle: 'meme' },
{ id: 'l7b', handle: '@daily.lol', niche: 'meme', followers: 142300, age: 42, price: 13200, er: 8.4, verified: false, badge: null, country: 'TR', postsStyle: 'meme' },
// Lifestyle — 2
{ id: 'l8', handle: '@room.tonic', niche: 'lifestyle', followers: 68900, age: 40, price: 10400, er: 5.3, verified: false, badge: null, country: 'TR', postsStyle: 'lifestyle' },
{ id: 'l8b', handle: '@slow.weekday', niche: 'lifestyle', followers: 52100, age: 27, price: 7800, er: 6.1, verified: false, badge: 'new', country: 'TR', postsStyle: 'lifestyle' },
// Beauty — 2
{ id: 'l9', handle: '@glow.mirror', niche: 'beauty', followers: 142600, age: 34, price: 19800, er: 4.6, verified: true, badge: null, country: 'TR', postsStyle: 'beauty' },
{ id: 'l9b', handle: '@blush.lab', niche: 'beauty', followers: 81700, age: 38, price: 12600, er: 5.5, verified: false, badge: null, country: 'TR', postsStyle: 'beauty' },
// Fitness — 2
{ id: 'l10', handle: '@lift.journal', niche: 'fitness', followers: 71400, age: 29, price: 8900, er: 6.1, verified: false, badge: 'new', country: 'TR', postsStyle: 'fitness' },
{ id: 'l10b',handle: '@move.method', niche: 'fitness', followers: 124800, age: 51, price: 18400, er: 5.2, verified: true, badge: null, country: 'TR', postsStyle: 'fitness' },
// Music — 2
{ id: 'l11', handle: '@low.cadence', niche: 'music', followers: 49800, age: 66, price: 11200, er: 4.0, verified: false, badge: null, country: 'TR', postsStyle: 'music' },
{ id: 'l11b',handle: '@indie.tape', niche: 'music', followers: 92400, age: 47, price: 14600, er: 4.8, verified: false, badge: null, country: 'TR', postsStyle: 'music' },
// Auto — 2
{ id: 'l12', handle: '@garagekeys', niche: 'auto', followers: 185200, age: 78, price: 27500, er: 3.4, verified: true, badge: 'rare', country: 'TR', postsStyle: 'auto' },
{ id: 'l12b',handle: '@apex.shift', niche: 'auto', followers: 64300, age: 35, price: 9400, er: 4.2, verified: false, badge: 'new', country: 'TR', postsStyle: 'auto' },
];
// Format large numbers: 128400 -> "128.4K"
function fmtNum(n, lang = 'tr') {
if (n < 1000) return String(n);
if (n < 1_000_000) return (n / 1000).toFixed(n < 10000 ? 1 : 0).replace(/\.0$/, '') + 'K';
return (n / 1_000_000).toFixed(1) + 'M';
}
function fmtPrice(n, lang = 'tr') {
const sep = lang === 'tr' ? '.' : ',';
const s = Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, sep);
return lang === 'tr' ? `${s} ₺` : `₺${s}`;
}
function fmtAge(months, lang = 'tr', L = I18N.tr) {
const y = Math.floor(months / 12);
const m = months % 12;
if (y === 0) return `${m} ${L.card_age_months}`;
if (m === 0) return `${y} ${L.card_age_years}`;
return `${y}${lang === 'tr' ? 'y' : 'y'} ${m}${lang === 'tr' ? 'a' : 'm'}`;
}
// Pseudo-hash for deterministic post patterns
function hashPost(seed, i) {
let h = 0;
const s = seed + ':' + i;
for (let k = 0; k < s.length; k++) h = (h * 31 + s.charCodeAt(k)) | 0;
return Math.abs(h);
}
// Generate a 9-grid of stylized "post" placeholders for a handle
function PostGrid({ handle, postsStyle, gap = 2, radius = 2, cols = 3, rows = 3, size }) {
const stops = NICHE_COLORS[postsStyle] || ['#888', '#444'];
const tiles = [];
for (let i = 0; i < cols * rows; i++) {
const h = hashPost(handle, i);
const variant = h % 5;
const angle = (h >> 3) % 360;
const shade = ((h >> 8) % 40) / 100; // 0..0.4
const bg = `linear-gradient(${angle}deg, ${stops[0]} 0%, ${stops[1]} 100%)`;
tiles.push(
{/* decorative shape overlays so posts feel varied */}
{variant === 0 &&
}
{variant === 1 &&
}
{variant === 2 &&
}
{variant === 3 &&
}
{variant === 4 &&
}
);
}
return (
{tiles}
);
}
// Avatar — gradient ring + niche-tinted circle with first letter of handle
function Avatar({ handle, postsStyle, size = 52, ring = true }) {
const stops = NICHE_COLORS[postsStyle] || ['#888', '#444'];
const letter = handle.replace('@', '')[0].toUpperCase();
const inner = (
{letter}
);
if (!ring) return inner;
return (
);
}
Object.assign(window, {
I18N, PALETTES, themeVars, Logo, Icon, injectGlobalStyles,
LISTINGS, NICHE_COLORS, fmtNum, fmtPrice, fmtAge, PostGrid, Avatar,
});