Logo GH

Olay-bot ve sohbet işlemleri

1) Amaç ve değer

Olay botu, doğrudan kurumsal sohbetten (Slack/Teams/Telegram) bir olay yönetimi arayüzüdür: Bir metin girişi - düzinelerce sistemde eylemler. Öyle mi:
  • Rutin otomatikleştirerek MTTA/MTTR'yi azaltır;
  • Tek bir olgu döngüsü (SoT) ve iletişim oluşturur;
  • Kanıtlanabilirlik sağlar (denetim, zaman çizelgeleri, SLA güncellemeleri);
  • Konsollardaki çağrı ve "gömme" yükünü azaltır.

2) Sohbet işlemlerinde roller ve RACI

Olay Komutanı (IC) - olay sahibi: açılış/kapanış, öncelik, çözümler.
Comms Lead (CL) - metinler ve güncelleme programı (harici/dahili).
Domain Leads (Ödemeler/Oyunlar/Çekirdek/Infra) - teknik gerçekler ve düzeltmeler.
Scribe - zaman çizelgesi, eylem günlüğü.
Bot Admin - bot hakları/politikaları/entegrasyonları.

Kural: Bir olayın bir IC ve bir CL'si vardır; Rol ters - açık bot komutu ile.

3) Uçtan uca senaryolar

1. Olayın başlangıcı: alert -'/incident new p1 "Deposits EU down" '- bot bir kart oluşturur, var-room, IC/CL atar, ilk güncelleme için bir zamanlayıcı ayarlar.
2. Ведение:'/incident add-facts ','/incident status set degraded','/incident assign @ payments-lead ','/incident timer 20m'.
3. İletişim:'/incident publish status '(taslak CL),'/incident partners notify','/incident regulator draft '.
4. Действия:'/runbook psp-failover PSP1 ^ PSP2 ','/feature toggle replay-center off 60m','/traffic shift 30 % eu ^ uk '.
5. Kapama ve ölüm sonrası:'/incident resolve ', otomatik toplama zaman çizelgesi,'/ölüm sonrası oluştur'.

4) Bot komutları (çekirdek)

Oluştur/Sınıflandırma

'/incident new p {1 | 2 | 3 | 4} "" [components =...] [bölgeler =...] '</p> <p>'/incident severity set p2 ','/incident tag add payments, psp'</p> <p>Mülkiyet ve roller</p> <p>'/incident ic @ user ','/incident comms @ user','/incident assign @ user [domain] '</p> <p>Zamanlayıcılar ve SLO güncellemeleri</p> <p><b>'/incident next-update 15m ','/incident remind' (bot pings CL),'/incident eta set 18:</b> 30 '</p> <p>Gerçekler ve durum</p> <p>'/incident fact 'auth-success PSP1 -25 % TR/EU "','/incident status {investigating 'degraded' monitoring 'resolved}'</p> <p>İletişim paketleri</p> <p>'/incident draft public 'partners' regulator ','/incident publish public'</p> <p>Entegrasyon</p> <p>'/runbook <action> ','/dashboard link <id>','/ticket create <system> '</p> <p>Kapanış/ölüm sonrası</p> <p>'/incident resolve [reason =...] ','/postmortem generate','/postmortem assign @ owner '</p> <div style="display:flex; align-items:flex-start; gap:12px; padding:12px 14px; margin:16px 0; border-radius:12px; background:#0f1624; color:#e6edf3; border-left:4px solid #2f9aff; border:1px solid rgba(255,255,255,0.05); box-shadow:0 0 12px rgba(255,255,255,.03);"> <span style="font-size:18px; line-height:1.2; color:#5ab8ff;">💡</span> <span style="flex:1; opacity:.95;">Tüm komutlar zaman, yazar, parametreler ve sonuç ile günlüğe kaydedilir.</span> </div> <p>5) Entegrasyonlar (minimum gerekli)</p> <p>İzleme: uyarılar, SLI/SLO (burn-rate), panolara bağlantılar.<br>Olay Yöneticisi (ITSM): Iki yönlü durum/alan senkronizasyonu.<br>Durum sayfası: CL (policy-gate) üzerinden taslaklar ve yayınlar.<br>Sağlayıcılar (PSP/KYC/Game Studios): iletişim dizinleri, hızlı mektuplar/kanallar.<br>Serbest Bırakma/Özellik Bayrakları: kanarya durur/geri çekilir, bağlantıları serbest bırakır.<br>Runbooks/Otomatik iyileştirme: Korkuluklu Güvenli Eylem Kataloğu.<br>CMDB/owners: Alan adının otomatik atanması, büyütme.<br>Zaman çizelgesi depolama: WORM/immutable for audit/post-mortems.</p> <p>6) Bot mimarisi</p> <p>Ağ Geçidi (Sohbet Adaptörü): Slack/Teams/Telegram arayüzleri.<br>Komut Ayrıştırıcı + İlke Motoru: yetkilendirme, doğrulama, SoD ve toleranslar.<br>Orkestratör: olay senaryoları, zamanlayıcılar, hatırlatıcılar.<br>Entegrasyonlar Katmanı: ITSM istemcileri, izleme, durum sayfası, sürümler, çalışma kitapları.<br>Kanıt Deposu: olaylar, gerçekler, mesaj yayılımları, ekler (WORM).<br>Metrikler ve Denetim: kalite metrikleri, eylem günlükleri, komut izleme.</p> <p>7) Politikalar, haklar ve güvenlik</p> <p>RBAC/ABAC: Oluşturabilen/kapatabilen, şiddeti değiştirebilen, dışarıda yayınlayabilen.<br>SoD: İletişim yayınlama CL rolü gerektirir; Yüksek riskli eylemler (PSP yönlendirme, PII dışa aktarma) - çift kontrol.<br>JIT hakları: Olay sırasında alan adı liderlerine geçici olarak verilmesi.<br>İmza ve şifreleme: Sistemlere webhooks/istekler - HMAC/mTLS.<br>Yağ-parmak koruması: tehlikeli komutların onaylanması, kuru çalıştırma ve eylem için TTL.<br>PII hijyeni: taslaklarda/günlüklerde maskeleme; Açık kanallarda PII inhibisyonu.</p> <p>8) Otomasyon akışları (örnek)</p> <p>Alert P1 - bot bir var-room ('# inc-2025-11-01-001'), nöbetçi ping (IC, CL, Payments/Infra) oluşturur.<br>Gösterge tablolarını/SLI'yi bağlar, ITSM'de bir bilet açar, ilk genel güncelleme için bir şablon hazırlar.<br>Zamanlayıcıları ayarlar:'bir sonraki güncelleme 15 dakika içinde ", CL hatırlatıcıları.<br>Предлагает çalışma kitapları: "PSP %30'u yeniden yönlendirdi" PSP2, "yeniden oynatma merkezini bozdu", "otomatik ölçekli işçiler".<br>Yayınlarken - metnin sürümünü düzeltir ve durum sayfasına/sosyal ağa (CL aracılığıyla) yayınlar.<br>Kapatırken - zaman çizelgesi, metrikler, ölüm sonrası taslak, VIP/ortak posta toplar.</p> <p>9) Zaman çizelgeleri ve kanıtlanabilirlik</p> <p>Her olay kaydedilir: 'T + mm: description, author/bot, command, result, links'.<br>Mesajların düzenlenmesi (diff), yayınlara/özellik bayraklarına/planlı çalışmalara bağlanması desteklenir.<br>Dışa aktarma: Denetim ve düzenleyiciler için PDF/CSV.</p> <p>10) Metrikler (KPI/KRI ChatOps)</p> <p>MTTA (chat):'/incident new 'uyarısı.<br>MTTS (kurulum): var odası hazır olmadan ve roller atanmadan önce.<br>Kadans uyumu: genel güncelleme aralıklarına bağlılık.<br>Runbook kullanım oranı: Otomatik aktivitelere sahip olayların oranı.<br>Tutarlılık puanı: Kanallar arasındaki tutarsızlıklar = 0 - hedef.<br>Çağrı cihazı fatigue↓: Aynı/daha iyi SLO ile azaltılmış manuel çağrı cihazları.<br>Postmortem SLA: D + 5 ≤ toplanan post-mortemlerin oranı.</p> <p>11) Şablon kataloğu (fragmanlar)</p> <u style="text-underline-offset:7px; text-decoration-color:rgba(200, 220, 255, 0.35);">P1 oluştur:</u> <pre style="margin:14px 0; padding:12px 14px; background:#0f1624; color:#e5e7eb; border:1px solid rgba(255,255,255,.10); border-radius:12px; overflow:auto; box-shadow:0 0 12px rgba(255,255,255,.03); direction:ltr; text-align:left; unicode-bidi:bidi-override;"><code> /incident new p1 "Deposits EU down" components=payments,deposits regions=EU</code></pre> <u style="text-underline-offset:7px; text-decoration-color:rgba(200, 220, 255, 0.35);">İlk genel güncelleme (CL aracılığıyla):</u> <pre style="margin:14px 0; padding:12px 14px; background:#0f1624; color:#e5e7eb; border:1px solid rgba(255,255,255,.10); border-radius:12px; overflow:auto; box-shadow:0 0 12px rgba(255,255,255,.03); direction:ltr; text-align:left; unicode-bidi:bidi-override;"><code> /incident draft public /incident publish public</code></pre> <u style="text-underline-offset:7px; text-decoration-color:rgba(200, 220, 255, 0.35);">PSP yönlendirme ve özellik bozulması:</u> <pre style="margin:14px 0; padding:12px 14px; background:#0f1624; color:#e5e7eb; border:1px solid rgba(255,255,255,.10); border-radius:12px; overflow:auto; box-shadow:0 0 12px rgba(255,255,255,.03); direction:ltr; text-align:left; unicode-bidi:bidi-override;"><code> /runbook psp-failover PSP1→PSP2 30% /feature toggle replay-center off 45m</code></pre> <u style="text-underline-offset:7px; text-decoration-color:rgba(200, 220, 255, 0.35);">Ölüm sonrası:</u> <pre style="margin:14px 0; padding:12px 14px; background:#0f1624; color:#e5e7eb; border:1px solid rgba(255,255,255,.10); border-radius:12px; overflow:auto; box-shadow:0 0 12px rgba(255,255,255,.03); direction:ltr; text-align:left; unicode-bidi:bidi-override;"><code> /postmortem generate /postmortem assign @owner</code></pre> <p>12) Süreçlere gömme</p> <p>İletişim: Olay İletişim ve Sistem Durum Sayfalarına Bağlantı.<br>Gözlemlenebilirlik: SLO/SLI ve sentetiklere hızlı bağlantılar; Grafiklerin otomatik bağlanması.<br>Uyarı: P1/P2 sırasında bir olayın otomatik olarak oluşturulması; Tek bir sinyal akışı.<br>Otomatik düzeltmeler: Korkulukları ve geri dönüşleri olan tek düğmeli çalışma kitapları.<br>İş Akışı Motoru: insan görevleri (4-göz), eskalasyon zamanlayıcıları, kontrol listeleri.</p> <p>13) Uygulama Yol Haritası (4-8 hafta)</p> <p>Ned. 1-2: MVP takımları:'/incident new ', roller (IC/CL), var odası, güncelleme zamanlayıcısı, ITSM ile iletişim ve izleme.<br>Ned. 3-4: mesaj şablonları (public/partners/regulators), durum sayfası (chernovik ^ publikatsiya), katalog 5-7 runbooks.<br>Ned. 5-6: policy-as-code (RBAC/SoD/JIT), yüksek riskli ikili kontrol, WORM dergisi, ChatOps KPI panosu.<br>Ned. 7-8: masa üstü P1/P2 egzersizleri, sürümler/özellik bayrakları ile entegrasyon, ölüm sonrası otomatik toplama, yerelleştirme.</p> <p>14) Antipatterns</p> <p>"Botun her yerinde" korkuluklar olmadan - rastgele tehlikeli eylemler.<br>CL/Legal-review rolü olmadan durum sayfasına gönderilir.<br>Günlükleri/sürümleri olmayan komutlar - kanıtlanamazlık.<br>Sohbette karmaşık formlar (20 + alan) - hız düşer; Daha iyi kısa komutlar + bağlantılar.<br>P1'de güncelleme zamanlayıcısı yok - "sessizlik".<br>CMDB/sahip entegrasyonunun olmaması - atama kaosu.</p> <p>15) Alt satır</p> <p>Incident-bot ve ChatOps bir "komutlu bot'değil, bir işletim platformudur: bir olaya hızlı bir başlangıç, disiplin güncellemesi, güvenli kısıtlamalarla otomatik eylemler, uçtan uca gözlemlenebilirlik ve kanıtlanabilirlik. Böyle bir devre tahmin edilebilir bir şekilde MTTR'yi azaltır, iletişim kalitesini artırır ve iGaming işinin gelirini yoğun zamanlarda korur.</p> </article> </div> </section> <style> /* =================== INPUT SYSTEM =================== */ .gh-input-h { --h:44px; height:var(--h); } .gh-chip, .gh-ccode, .gh-text, .gh-tel{ height:var(--h); padding:.55rem .75rem; border-radius:1rem; font-size:.875rem; line-height:1.2; border:1px solid rgba(255,255,255,.15); background:rgba(16,26,46,.6); color:#fff; outline:none; transition:border-color .15s ease, box-shadow .15s ease, background-color .15s ease; } .gh-chip{ width:4.5rem; display:inline-flex; align-items:center; justify-content:center; color:#cbd5e1; } .gh-ccode{ width:4.5rem; text-align:center; } .gh-ccode[readonly]{ color:#cbd5e1; cursor:default; user-select:none; } .gh-text, .gh-tel{ flex:1; min-width:0; } .gh-group{ display:flex; gap:.5rem; align-items:stretch; } .gh-focus:focus{ box-shadow:0 0 0 2px rgba(47,154,255,.25); border-color:rgba(47,154,255,.55); } /* =================== LABEL ROW =================== */ .gh-label-row{ display:grid; grid-template-columns: auto max-content; align-items:center; gap:.5rem; margin-bottom:.375rem; } .gh-label-row .gh-label{ font-size:.75rem; color:#94a3b8; } .gh-label-row .gh-ghost{ justify-self:end; text-align:center; white-space:nowrap; font-size:.675rem; letter-spacing:.06em; text-transform:uppercase; border:1px dashed rgba(255,255,255,.18); color:#9aa4b2; border-radius:999px; padding:.15rem .45rem; opacity:.85; transition:opacity .2s ease, color .2s ease, border-color .2s ease; pointer-events:none; } .gh-label-row .gh-ghost.-stub{ opacity:0; border-color:transparent; } .gh-optional.is-active .gh-ghost{ opacity:.25; color:#94a3b8; border-color:rgba(255,255,255,.12); } .gh-error{ box-shadow:0 0 0 2px rgba(244,63,94,.45)!important; border-color:rgba(244,63,94,.6)!important; } .gh-field + .gh-field{ margin-top:.9rem; } .gh-hint{ margin-top:.35rem; font-size:.6875rem; color:#94a3b8; } @media (max-width: 360px){ .gh-chip, .gh-ccode{ width:4.1rem; } } /* ====== КНОПКА быстрых контактов ====== */ /* Базово — ведёт себя как обычная inline-flex кнопка */ .gh-qs-trigger { position: relative; /* z-index не задаём, чтобы не лезла выше оверлеев */ transition: opacity .2s ease, transform .2s ease, visibility .2s ease; } /* Когда открыт теговый список — кнопку полностью прячем */ html.tag-modal-open .gh-qs-trigger { pointer-events: none; opacity: 0; visibility: hidden; transform: translateY(4px); } </style> <!-- CONTACT --> <section id="contact" class="py-12 lg:py-16"> <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <!-- Заголовок --> <div class="mb-4"> <div class="-mx-4 dir-ltr"> <span class="ml-4 text-[10px] uppercase tracking-widest font-semibold text-aqua-300 bg-aqua-500/10 border border-aqua-500/30 rounded-full px-2 py-0.5 inline-block mb-2"> Contact </span> <div class="fade-mask"> <div class="fade-inner gh-scrollbar-none touch-x px-4"> <h2 class="text-3xl font-bold text-white drop-shadow-[0_0_8px_rgba(47,154,255,0.35)] whitespace-nowrap"> Bizimle iletişime geçin </h2> </div> </div> </div> </div> <div class="mt-8 grid md:grid-cols-2 gap-10 lg:gap-12 items-start"> <!-- Левая колонка --> <div class="space-y-3 text-sm"> <p class="text-slate-400 text-[13px] pl-0 sm:pl-[2.4rem]" style="line-height:1.25; margin-bottom:18px;"> <span style="display:block; font-weight:800; margin-bottom:11px;">Her türlü soru veya destek için bize ulaşın.</span><span style="display:block; height:5px;"></span><span style="display:block; font-weight:400;">Size yardımcı olmaya her zaman hazırız!</span> </p> <!-- Email --> <div class="flex items-start gap-2"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path d="M.05 3.555A2 2 0 0 1 2 2h12a2 2 0 0 1 1.95 1.555L8 8.414zM0 4.697v7.104l5.803-3.558zM6.761 8.83l-6.57 4.027A2 2 0 0 0 2 14h12a2 2 0 0 0 1.808-1.144l-6.57-4.027L8 9.586zm3.436-.586L16 11.801V4.697z"/> </svg> <div> <div class="font-medium text-white">Email</div> <a href="mailto:support@gamblehub.io?subject=Gamble%20Hub%20i%C3%A7in%20Talep&body=Merhaba%21%0A%0A%C3%9Cr%C3%BCn%C3%BCn%C3%BCz%2Fhizmetiniz%20hakk%C4%B1nda%20bir%20soru%20sormak%20istiyorum.%0A%0AL%C3%BCtfen%20uygun%20oldu%C4%9Funuz%20bir%20zamanda%20benimle%20ileti%C5%9Fime%20ge%C3%A7in.%0A%0ATe%C5%9Fekk%C3%BCrler%21" class="text-slate-400 text-xs break-all hover:text-aqua-400 transition dir-ltr" rel="nofollow noopener"> support@gamblehub.io </a> </div> </div> <!-- Telegram --> <div class="flex items-start gap-2"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.287 5.906q-1.168.486-4.666 2.01-.567.225-.595.442c-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294q.39.01.868-.32 3.269-2.206 3.374-2.23c.05-.012.12-.026.166.016s.042.12.037.141c-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8 8 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629q.14.092.27.187c.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.4 1.4 0 0 0-.013-.315.34.34 0 0 0-.114-.217.53.53 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09"/> </svg> <div> <div class="font-medium text-white">Telegram</div> <a href="https://t.me/Gamble_GC" target="_blank" rel="nofollow noopener noreferrer" class="text-slate-400 text-xs hover:text-aqua-400 transition dir-ltr"> @Gamble_GC </a> </div> </div> <!-- WhatsApp --> <div class="flex items-start gap-2 mt-3"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path d="M13.601 2.326A7.85 7.85 0 0 0 7.994 0C3.627 0 .068 3.558.064 7.926c0 1.399.366 2.76 1.057 3.965L0 16l4.204-1.102a7.9 7.9 0 0 0 3.79.965h.004c4.368 0 7.926-3.558 7.93-7.93A7.9 7.9 0 0 0 13.6 2.326zM7.994 14.521a6.6 6.6 0 0 1-3.356-.92l-.240-.144-2.494.654.666-2.433-.156-.251a6.56 6.56 0 0 1-1.007-3.505c0-3.626 2.957-6.584 6.591-6.584a6.56 6.56 0 0 1 4.66 1.931 6.56 6.56 0 0 1 1.928 4.66c-.004 3.639-2.961 6.592-6.592 6.592m3.615-4.934c-.197-.099-1.17-.578-1.353-.646-.182-.065-.315-.099-.445.099-.133.197-.513.646-.627.775-.114.133-.232.148-.43.05-.197-.1-.836-.308-1.592-.985-.59-.525-.985-1.175-1.103-1.372-.114-.198-.011-.304.088-.403.087-.088.197-.232.296-.346.10-.114.133-.198.198-.33.065-.134.034-.248-.015-.347-.05-.099-.445-1.076-.612-1.47-.16-.389-.323-.335-.445-.34-.114-.007-.247-.007-.38-.007a.73.73 0 0 0-.529.247c-.182.198-.691.677-.691 1.654s.71 1.916.81 2.049c.098.133 1.394 2.132 3.383 2.992.47.205.84.326 1.129.418.475.152.904.129 1.246.08.38-.058 1.171-.48 1.338-.943.164-.464.164-.86.114-.943-.049-.084-.182-.133-.38-.232"/> </svg> <div> <div class="font-medium text-white">WhatsApp</div> <a href="https://wa.me/447893931020" target="_blank" rel="nofollow noopener noreferrer" class="text-slate-400 text-xs hover:text-aqua-400 transition dir-ltr"> +44 7893 931 020 </a> </div> </div> <!-- Триггер быстрой формы --> <a href="#" class="gh-qs-trigger relative inline-flex items-center justify-center gap-2 px-5 pr-10 py-2 rounded-full text-[13px] font-semibold text-white border border-white/10 bg-gradient-to-r from-aqua-600/40 to-brand-500/40 hover:from-aqua-600/60 hover:to-brand-500/60 focus:outline-none focus:ring-2 focus:ring-aqua-400/50 transition-all duration-200" data-qs-open data-qs-subject="Quick: Bizimle iletişime geçin" data-qs-pref="email"> <span class="relative z-10"> Entegrasyona başla </span> <!-- SVG "палец" --> <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" class="absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 opacity-95 pointer-events-none drop-shadow-[0_2px_8px_rgba(47,154,255,0.35)]"> <path d="M8.5 1.75v2.716l.047-.002c.312-.012.742-.016 1.051.046.28.056.543.18.738.288.273.152.456.385.56.642l.132-.012c.312-.024.794-.038 1.158.108.37.148.689.487.88.716q.113.137.195.248h.582a2 2 0 0 1 1.99 2.199l-.272 2.715a3.5 3.5 0 0 1-.444 1.389l-1.395 2.441A1.5 1.5 0 0 1 12.42 16H6.118a1.5 1.5 0 0 1-1.342-.83l-1.215-2.43L1.07 8.589a1.517 1.517 0 0 1 2.373-1.852L5 8.293V1.75a1.75 1.75 0 0 1 3.5 0"/> </svg> </a> </div> <!-- Правая колонка: форма --> <div class="relative rounded-3xl border border-white/15 bg-gradient-to-br from-night-800/80 to-night-900/80 p-6 backdrop-blur-sm"> <!-- Toast для основной формы --> <div id="ghToastMain" class="pointer-events-none absolute inset-x-4 top-[30rem] z-50 -translate-y-2 opacity-0 transition-all duration-300"></div> <form id="ghContactForm" class="space-y-4" autocomplete="off" novalidate> <!-- honeypot / служебные --> <input type="text" name="company" tabindex="-1" autocomplete="off" class="hidden" aria-hidden="true"/> <input type="hidden" name="started_at" id="ghStartedAt"/> <input type="hidden" name="channel" id="ghChannel" value="email"/> <input type="hidden" name="whatsapp_full" id="ghWaFull" value=""/> <p class="text-xs text-slate-400"> Email — <span class="text-slate-200">zorunlu</span>. Telegram veya WhatsApp — <span class="text-slate-300">isteğe bağlı</span>. </p> <!-- Name --> <div class="gh-field" data-field="name"> <div class="gh-label-row"> <span class="gh-label">Adınız</span> <span class="gh-ghost -stub">zorunlu değil</span> </div> <input id="name" name="name" type="text" required class="gh-text gh-input-h gh-focus w-full" placeholder="Ahmet Yılmaz"/> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <!-- Email --> <div class="gh-field" data-field="email" id="fieldEmail"> <div class="gh-label-row"> <span class="gh-label">Email</span> <span class="gh-ghost -stub">zorunlu değil</span> </div> <input id="email" name="email" type="email" required class="gh-text gh-input-h gh-focus w-full" placeholder="you@company.com"/> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <!-- Subject --> <div class="gh-field" data-field="subject"> <div class="gh-label-row"> <span class="gh-label">Konu</span> <span class="gh-ghost -stub">zorunlu değil</span> </div> <input id="subject" name="subject" type="text" required class="gh-text gh-input-h gh-focus w-full" placeholder="Entegrasyon, fiyatlandırma, iş ortaklığı hakkında talep…"/> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <!-- Message --> <div class="gh-field" data-field="message"> <div class="gh-label-row"> <span class="gh-label">Mesaj</span> <span class="gh-ghost -stub">zorunlu değil</span> </div> <textarea id="message" name="message" rows="4" required class="w-full rounded-2xl border border-white/15 bg-night-700/60 px-3 py-2 text-sm text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-aqua-500/40" placeholder="İhtiyacınızı kısaca anlatın"></textarea> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <!-- Telegram (optional) --> <div class="gh-field gh-optional" data-field="telegram" id="fieldTelegram"> <div class="gh-label-row"> <span class="gh-label">Telegram</span> <span class="gh-ghost">zorunlu değil</span> </div> <div class="gh-group"> <span class="gh-chip gh-input-h">@</span> <input id="telegram" name="telegram" type="text" inputmode="text" autocomplete="off" maxlength="32" class="gh-text gh-input-h gh-focus" placeholder="your_handle"/> </div> <div class="gh-hint">Telegram belirtirseniz, Email’e ek olarak oradan da yanıt veririz.</div> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <!-- WhatsApp (optional) --> <div class="gh-field gh-optional" data-field="whatsapp" id="fieldWhatsApp"> <div class="gh-label-row"> <span class="gh-label">WhatsApp</span> <span class="gh-ghost">zorunlu değil</span> </div> <div class="gh-group"> <input id="ccode" name="ccode" type="text" value="+" readonly tabindex="-1" aria-readonly="true" class="gh-ccode gh-input-h dir-ltr"/> <input id="whatsapp" name="whatsapp" type="tel" inputmode="tel" autocomplete="tel" maxlength="24" class="gh-tel gh-input-h gh-focus dir-ltr" placeholder="380 00 000 00 00"/> </div> <div class="gh-hint dir-ltr">Format: +ülke kodu ve numara (örneğin, +90XXXXXXXXX).</div> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <!-- Submit --> <div class="pt-2"> <button id="ghSubmitBtn" type="submit" class="w-full rounded-2xl border border-aqua-500/40 bg-gradient-to-r from-aqua-600 to-brand-500 px-4 py-2 text-sm font-semibold text-white hover:from-aqua-500 hover:to-brand-400 focus:outline-none focus:ring-2 focus:ring-aqua-400/50 transition-all duration-200"> Talep gönder </button> <p class="mt-2 text-[11px] text-slate-500">Butona tıklayarak veri işlemenize onay vermiş olursunuz.</p> </div> </form> </div> </div> </div> </section> <!-- === Модалка быстрой формы === --> <div id="ghQuickModal" class="fixed inset-0 z-[90] hidden"> <!-- overlay --> <div id="ghQuickOverlay" class="absolute inset-0 bg-black/60 backdrop-blur-sm opacity-0 transition-opacity duration-200"></div> <!-- dialog --> <div class="absolute inset-0 p-4 flex items-center justify-center"> <div id="ghQuickPanel" class="relative w-full max-w-md rounded-3xl border border-white/15 bg-gradient-to-br from-night-800/80 to-night-900/80 backdrop-blur-sm opacity-0 translate-y-2 scale-[.98] transition-[opacity,transform] duration-200" style="max-height:calc(100vh - 2rem); overflow-y:auto; -webkit-overflow-scrolling:touch;"> <!-- Toast для модалки --> <div id="ghToastQuick" class="pointer-events-none absolute inset-x-4 top-4 z-20 -translate-y-2 opacity-0 transition-all duration-300"></div> <!-- header --> <div class="flex items-start justify-between p-4 pb-3"> <div> <h3 class="text-lg font-semibold text-white">Hızlı iletişim</h3> </div> <button type="button" id="ghQuickClose" class="rounded-xl p-2 text-slate-300 hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-aqua-500/40" aria-label="Close"> <svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="m12 10.586 4.95-4.95 1.414 1.414L13.414 12l4.95 4.95-1.414 1.414L12 13.414l-4.95 4.95-1.414-1.414L10.586 12l-4.95-4.95L7.05 5.636z"/></svg> </button> </div> <!-- STATES WRAPPER --> <div class="p-4 pt-0"> <!-- FORM STATE --> <form id="ghQuickForm" class="space-y-4" autocomplete="off" novalidate> <!-- honeypot + служебные --> <input type="text" name="company" class="hidden" tabindex="-1" aria-hidden="true"> <input type="hidden" name="started_at" id="ghQuickStartedAt"> <input type="hidden" name="channel" id="ghQuickChannel" value="email"> <input type="hidden" name="subject" id="ghQuickSubject" value=""> <p class="text-xs text-slate-400 leading-relaxed"> Enter at least one contact. </p> <!-- Email --> <div class="gh-field" data-field="quick-email"> <label for="ghQuickEmail" class="block text-xs text-slate-400 mb-1">Email</label> <input id="ghQuickEmail" name="email" type="email" inputmode="email" autocomplete="email" maxlength="128" class="gh-text gh-input-h gh-focus w-full dir-ltr" placeholder="you@company.com"> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <!-- Telegram --> <div class="gh-field" data-field="quick-telegram"> <label for="ghQuickTelegram" class="block text-xs text-slate-400 mb-1">Telegram</label> <div class="gh-group"> <span class="gh-chip gh-input-h dir-ltr" aria-hidden="true">@</span> <input id="ghQuickTelegram" name="telegram" type="text" inputmode="text" autocomplete="off" maxlength="32" class="gh-text gh-input-h gh-focus dir-ltr" placeholder="your_handle"> </div> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <!-- WhatsApp --> <div class="gh-field" data-field="quick-whatsapp"> <label for="ghQuickWhatsApp" class="block text-xs text-slate-400 mb-1">WhatsApp</label> <div class="gh-group"> <span class="gh-chip gh-input-h dir-ltr" aria-hidden="true">+</span> <input id="ghQuickWhatsApp" name="whatsapp" type="tel" inputmode="tel" autocomplete="tel" maxlength="24" class="gh-tel gh-input-h gh-focus dir-ltr" placeholder="380 00 000 00 00"> </div> <div class="gh-error-slot mt-1 text-[11px] leading-4 text-rose-300 hidden"></div> </div> <div id="ghQuickContactsError" class="hidden rounded-xl border border-rose-400/30 bg-rose-500/10 px-3 py-2 text-[11px] leading-4 text-rose-300"> </div> <!-- Кнопка --> <div class="pt-2 pb-1"> <button id="ghQuickSend" type="submit" class="w-full rounded-2xl border border-aqua-500/40 bg-gradient-to-r from-aqua-600 to-brand-500 px-4 py-2 text-sm font-semibold text-white hover:from-aqua-500 hover:to-brand-400 focus:outline-none focus:ring-2 focus:ring-aqua-400/50 transition-all duration-200"> Gönder </button> </div> </form> <!-- SUCCESS STATE --> <div id="ghQuickSuccess" class="hidden"> <div class="rounded-2xl border border-white/10 bg-white/5 p-4"> <div class="flex items-start gap-3"> <div class="shrink-0 mt-0.5"> <svg width="20" height="20" viewBox="0 0 24 24" class="opacity-90"><path fill="currentColor" d="M9.5 16.2 5.8 12.5l-1.6 1.6L9.5 20 20 9.5l-1.6-1.6z"/></svg> </div> <div class="text-sm"> <div class="font-semibold text-white">Talep gönderildi</div> <p class="mt-1 text-slate-300">Teşekkürler! İletişim bilginizi aldık ve kısa süre içinde size dönüş yapacağız. Yanıt, seçtiğiniz kanala gönderilecektir.</p> </div> </div> </div> <div class="mt-3 flex gap-2"> <button id="ghQuickOk" type="button" class="flex-1 rounded-2xl border border-white/15 bg-white/10 px-4 py-2 text-sm text-white hover:bg-white/15 focus:outline-none focus:ring-2 focus:ring-aqua-400/40"> Tamam / kapat </button> <button id="ghQuickAgain" type="button" class="rounded-2xl border border-aqua-500/40 bg-gradient-to-r from-aqua-600 to-brand-500 px-4 py-2 text-sm font-semibold text-white hover:from-aqua-500 hover:to-brand-400 focus:outline-none focus:ring-2 focus:ring-aqua-400/50"> Yeni talep </button> </div> </div> </div> <!-- /STATES WRAPPER --> </div> </div> </div> <script> /* ========= единый helper для тостов ========= */ function ghShowToastIn(container, type, text) { const el = (typeof container === 'string') ? document.getElementById(container) : container; if (!el) return; const base = 'inline-flex items-start gap-3 rounded-2xl px-4 py-3 shadow-glow border ring-1 backdrop-blur-md ' + 'absolute right-0'; const ok = 'bg-teal-600/80 border-teal-400/60 text-white'; const err = 'bg-rose-500/80 border-rose-400/60 text-white'; el.innerHTML = ` <div class="${base} ${type === 'ok' ? ok : err} dir-ltr"> <div class="mt-0.5 shrink-0"> ${ type === 'ok' ? '<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M9.5 16.2 5.8 12.5l-1.6 1.6L9.5 20 20 9.5l-1.6-1.6z"/></svg>' : '<svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2a10 10 0 1 0 .001 20.001A10 10 0 0 0 12 2m1 14h-2v-2h2zm0-4h-2V7h2z"/></svg>' } </div> <div class="text-sm leading-5">${text}</div> </div>`; el.style.opacity = '1'; el.style.transform = 'translateY(0)'; clearTimeout(el._t); el._t = setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateY(-20px)'; }, 3500); } /* ========= МОДАЛКА: quick contact ========= */ document.addEventListener('DOMContentLoaded', () => { const $modal = document.getElementById('ghQuickModal'); const $overlay = document.getElementById('ghQuickOverlay'); const $panel = document.getElementById('ghQuickPanel'); const $openers = document.querySelectorAll('[data-qs-open]'); const $close = document.getElementById('ghQuickClose'); const $form = document.getElementById('ghQuickForm'); const $success = document.getElementById('ghQuickSuccess'); const $okBtn = document.getElementById('ghQuickOk'); const $againBtn = document.getElementById('ghQuickAgain'); const $email = document.getElementById('ghQuickEmail'); const $telegram = document.getElementById('ghQuickTelegram'); const $whatsapp = document.getElementById('ghQuickWhatsApp'); const $contactsError = document.getElementById('ghQuickContactsError'); const $channel = document.getElementById('ghQuickChannel'); const $subject = document.getElementById('ghQuickSubject'); const $started = document.getElementById('ghQuickStartedAt'); const $sendBtn = document.getElementById('ghQuickSend'); const emailLike = s => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s); const tgValid = s => /^[A-Za-z0-9_]{5,}$/.test(s); const waValid = s => /^\+\d{7,15}$/.test(s); function maskEmail(v) { v = (v || '').normalize('NFKC'); v = v.replace(/[^\x20-\x7E]/g, ''); return v.replace(/\s+/g, '').slice(0, 128); } function maskTelegram(v) { v = (v || '').replace(/^@+/, ''); return v.replace(/[^A-Za-z0-9_]/g, '').slice(0, 32); } function maskWhatsapp(v) { const digits = (v || '').replace(/\D/g, '').slice(0, 15); const groups = []; for (let i = 0; i < digits.length; i += 3) { groups.push(digits.slice(i, i + 3)); } return groups.join(' '); } function qGetSlot($el) { const wrap = $el.closest('.gh-field') || $el.parentElement; let slot = wrap.querySelector('.gh-error-slot'); if (!slot) { slot = document.createElement('div'); slot.className = 'gh-error-slot mt-1 text-[11px] leading-4 text-rose-300'; wrap.appendChild(slot); } return slot; } function qMarkError($el, msg) { if (!$el) return; $el.classList.add('gh-error'); $el.setAttribute('aria-invalid', 'true'); const slot = qGetSlot($el); slot.textContent = msg || ''; slot.classList.remove('hidden'); } function qClearError($el) { if (!$el) return; $el.classList.remove('gh-error'); $el.removeAttribute('aria-invalid'); const wrap = $el.closest('.gh-field') || $el.parentElement; const slot = wrap.querySelector('.gh-error-slot'); if (slot) { slot.textContent = ''; slot.classList.add('hidden'); } } function clearContactsError() { if (!$contactsError) return; $contactsError.textContent = ''; $contactsError.classList.add('hidden'); } function showContactsError(text) { if (!$contactsError) return; $contactsError.textContent = text; $contactsError.classList.remove('hidden'); } function clearAllQuickErrors() { [$email, $telegram, $whatsapp].forEach(qClearError); clearContactsError(); } function clearQuickContacts() { if ($email) $email.value = ''; if ($telegram) $telegram.value = ''; if ($whatsapp) $whatsapp.value = ''; clearAllQuickErrors(); } function showFormState() { $success.classList.add('hidden'); $form.classList.remove('hidden'); $sendBtn.disabled = false; $sendBtn.innerHTML = 'Gönder'; $started.value = String(Date.now()); setTimeout(() => $email.focus(), 60); } function showSuccessState() { $form.classList.add('hidden'); $success.classList.remove('hidden'); setTimeout(() => $okBtn.focus(), 30); } function openModal(subj) { clearQuickContacts(); $subject.value = subj || 'Quick Request'; $channel.value = 'email'; $started.value = String(Date.now()); showFormState(); $modal.classList.remove('hidden'); requestAnimationFrame(() => { $overlay.classList.remove('opacity-0'); $panel.classList.remove('opacity-0', 'translate-y-2', 'scale-[.98]'); }); setTimeout(() => $email.focus(), 120); } function closeModal() { $overlay.classList.add('opacity-0'); $panel.classList.add('opacity-0', 'translate-y-2', 'scale-[.98]'); setTimeout(() => { $modal.classList.add('hidden'); }, 200); } $openers.forEach(el => { el.addEventListener('click', e => { e.preventDefault(); openModal(el.getAttribute('data-qs-subject')); }); }); $close.addEventListener('click', closeModal); $okBtn.addEventListener('click', closeModal); $againBtn.addEventListener('click', () => { clearQuickContacts(); showFormState(); }); $modal.addEventListener('click', e => { if (e.target === $modal) closeModal(); }); document.addEventListener('keydown', e => { if (!$modal.classList.contains('hidden') && e.key === 'Escape') { closeModal(); } }); $email.addEventListener('input', () => { $email.value = maskEmail($email.value); qClearError($email); clearContactsError(); }); $telegram.addEventListener('input', () => { $telegram.value = maskTelegram($telegram.value); qClearError($telegram); clearContactsError(); }); $whatsapp.addEventListener('input', () => { $whatsapp.value = maskWhatsapp($whatsapp.value); qClearError($whatsapp); clearContactsError(); }); [$email, $telegram, $whatsapp].forEach($el => { $el.addEventListener('focus', () => { qClearError($el); clearContactsError(); }); }); $form.addEventListener('submit', async e => { e.preventDefault(); clearAllQuickErrors(); const emailVal = maskEmail($email.value.trim()); const telegramHandle = maskTelegram($telegram.value.trim()); const whatsappDigits = $whatsapp.value.replace(/\D/g, '').slice(0, 15); $email.value = emailVal; $telegram.value = telegramHandle; $whatsapp.value = maskWhatsapp(whatsappDigits); const hasEmail = emailVal.length > 0; const hasTelegram = telegramHandle.length > 0; const hasWhatsApp = whatsappDigits.length > 0; if (!hasEmail && !hasTelegram && !hasWhatsApp) { const text = 'Enter at least one contact: email, Telegram or WhatsApp.'; showContactsError(text); ghShowToastIn('ghToastQuick', 'err', text); return; } let hasError = false; if (hasEmail && !emailLike(emailVal)) { qMarkError($email, 'Invalid email'); hasError = true; } if (hasTelegram && !tgValid(telegramHandle)) { qMarkError($telegram, 'Only letters, digits and _ (min 5)'); hasError = true; } const whatsappFull = hasWhatsApp ? ('+' + whatsappDigits) : ''; if (hasWhatsApp && !waValid(whatsappFull)) { qMarkError($whatsapp, 'Invalid WhatsApp number'); hasError = true; } if (hasError) { ghShowToastIn('ghToastQuick', 'err', 'Check the contact details.'); return; } const primaryChannel = hasEmail ? 'email' : (hasTelegram ? 'telegram' : 'whatsapp'); $channel.value = primaryChannel; const fd = new FormData(); fd.append('started_at', $started.value || String(Date.now())); fd.append('company', ''); fd.append('channel', primaryChannel); fd.append('subject', $subject.value || 'Quick Request'); fd.append( 'message', 'Quick contact request.\nAt least one contact method was provided.\n\nThis message was sent from the quick modal.' ); if (hasEmail) fd.append('email', emailVal); if (hasTelegram) fd.append('telegram', '@' + telegramHandle); if (hasWhatsApp) fd.append('whatsapp_full', whatsappFull); const old = $sendBtn.innerHTML; $sendBtn.disabled = true; $sendBtn.innerHTML = 'Sending…'; try { const res = await fetch('/ajax/contact.php', { method: 'POST', headers: {'X-Requested-With': 'XMLHttpRequest'}, body: fd, credentials: 'same-origin' }); const data = await res.json().catch(() => ({})); if (!res.ok || !data.success) { if (res.status === 429 && data.retry_after) { ghShowToastIn( 'ghToastQuick', 'err', data.error_human || ('Please wait ' + data.retry_after + 's') ); } else { ghShowToastIn( 'ghToastQuick', 'err', (data && (data.error_human || data.error)) || ('HTTP ' + res.status) ); } return; } ghShowToastIn('ghToastQuick', 'ok', 'Talep gönderildi. En kısa sürede sizinle iletişime geçeceğiz.'); clearQuickContacts(); showSuccessState(); } catch (error) { ghShowToastIn('ghToastQuick', 'err', 'Network error. Try again later.'); } finally { $sendBtn.disabled = false; $sendBtn.innerHTML = old; } }); $started.value = String(Date.now()); }); /* ========= ОСНОВНАЯ ФОРМА ========= */ document.addEventListener('DOMContentLoaded', () => { const $form = document.getElementById('ghContactForm'); const $btn = document.getElementById('ghSubmitBtn'); const $name = document.getElementById('name'); const $email = document.getElementById('email'); const $subject = document.getElementById('subject'); const $message = document.getElementById('message'); const $tg = document.getElementById('telegram'); const $ccode = document.getElementById('ccode'); const $whats = document.getElementById('whatsapp'); const $waFull = document.getElementById('ghWaFull'); const $channel = document.getElementById('ghChannel'); const $started = document.getElementById('ghStartedAt'); const asciiOnly = s => s.replace(/[^\x20-\x7E]/g,''); const emailLike = s => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s); const tgValid = s => /^[A-Za-z0-9_]{5,}$/.test(s); const onlyDigits = s => s.replace(/\D+/g,''); const phoneFullValid = s => /^\+\d{7,15}$/.test(s); function getSlot($el) { const wrap = $el.closest('.gh-field') || $el.parentElement; let slot = wrap.querySelector('.gh-error-slot'); if (!slot) { slot = document.createElement('div'); slot.className = 'gh-error-slot mt-1 text-[11px] leading-4 text-rose-300'; wrap.appendChild(slot); } return slot; } function markError($el, msg) { if (!$el) return; $el.classList.add('gh-error'); $el.setAttribute('aria-invalid','true'); const slot = getSlot($el); slot.textContent = msg; slot.classList.remove('hidden'); } function clearError($el) { if (!$el) return; $el.classList.remove('gh-error'); $el.removeAttribute('aria-invalid'); const wrap = $el.closest('.gh-field') || $el.parentElement; const slot = wrap.querySelector('.gh-error-slot'); if (slot) { slot.textContent = ''; slot.classList.add('hidden'); } } [$name,$email,$subject,$message,$tg,$ccode,$whats].forEach(($el)=>{ if(!$el) return; $el.addEventListener('input', () => clearError($el)); $el.addEventListener('focus', () => clearError($el)); }); (function(){ const optWraps = document.querySelectorAll('.gh-optional'); const digits = s => (s||'').replace(/\D+/g,''); const refresh = (wrap)=>{ const hasFocus = document.activeElement && wrap.contains(document.activeElement); let hasVal = false; if (wrap.id === 'fieldWhatsApp') { const tel = wrap.querySelector('#whatsapp'); hasVal = digits(tel && tel.value).length > 0; } else if (wrap.id === 'fieldTelegram') { const tg = wrap.querySelector('#telegram'); hasVal = !!(tg && tg.value && tg.value.trim().length > 0); } else { const inputs = wrap.querySelectorAll('input,textarea'); hasVal = Array.from(inputs).some(i => (i.value||'').trim().length>0); } wrap.classList.toggle('is-active', hasFocus || hasVal); }; optWraps.forEach(wrap=>{ refresh(wrap); wrap.addEventListener('focusin', ()=>refresh(wrap)); wrap.addEventListener('focusout', ()=>setTimeout(()=>refresh(wrap), 10)); wrap.querySelectorAll('input,textarea').forEach(i=>{ i.addEventListener('input', ()=>refresh(wrap)); i.addEventListener('change', ()=>refresh(wrap)); i.addEventListener('keyup', ()=>refresh(wrap)); }); }); })(); if ($started) $started.value = String(Date.now()); if ($ccode) { $ccode.value = '+'; $ccode.readOnly = true; $ccode.setAttribute('aria-readonly','true'); $ccode.setAttribute('tabindex','-1'); } if ($whats) { $whats.setAttribute('autocomplete','tel'); $whats.maxLength = 24; $whats.addEventListener('input', () => { let v = $whats.value.replace(/[^\d()\-\s]/g, ''); v = v.replace(/\s{2,}/g, ' '); $whats.value = v.slice(0, 24); }); } if ($email) { $email.setAttribute('autocomplete','email'); $email.maxLength = 128; $email.addEventListener('input', () => { let v = $email.value.replace(/[^\x20-\x7E]/g, ''); v = v.replace(/\s+/g, ''); $email.value = v; }); } if ($tg) { $tg.setAttribute('autocomplete','off'); $tg.maxLength = 32; $tg.addEventListener('input', () => { let v = $tg.value.trim(); if (v.startsWith('@')) v = v.slice(1); v = v.replace(/[^A-Za-z0-9_]/g, ''); $tg.value = v; }); } $form.addEventListener('submit', async (e) => { e.preventDefault(); [$name,$email,$subject,$message,$tg,$ccode,$whats].forEach(clearError); const nameVal = ($name.value || '').trim(); const emailVal = asciiOnly(($email.value || '').trim()); const subjVal = ($subject.value || '').trim(); const msgVal = ($message.value || '').trim(); let tgVal = ($tg ? $tg.value.trim() : ''); if (tgVal.startsWith('@')) tgVal = tgVal.slice(1); tgVal = tgVal.replace(/[^A-Za-z0-9_]/g,''); const telDigits = onlyDigits($whats ? $whats.value : ''); const waFull = '+' + telDigits; if (nameVal.length < 2) { markError($name,'Enter your name (min 2 chars)'); ghShowToastIn('ghToastMain','err','Please enter your name.'); return; } if (!emailLike(emailVal)) { markError($email,'Invalid email'); ghShowToastIn('ghToastMain','err','Invalid email.'); return; } if (subjVal.length < 2) { markError($subject,'Subject is required'); ghShowToastIn('ghToastMain','err','Please specify a subject.'); return; } if (msgVal.length < 5) { markError($message,'Message is too short'); ghShowToastIn('ghToastMain','err','Message is too short.'); return; } if ($whats && telDigits.length > 0) { if (!phoneFullValid(waFull)) { markError($whats,'Invalid phone format'); ghShowToastIn('ghToastMain','err','Invalid phone number.'); return; } } if ($tg && tgVal && !tgValid(tgVal)) { markError($tg,'Only letters, digits and _ (min 5)'); ghShowToastIn('ghToastMain','err','Invalid Telegram handle.'); return; } if ($waFull) $waFull.value = (telDigits.length ? waFull : ''); if ($channel) $channel.value = 'email'; if ($started && !$started.value) $started.value = String(Date.now()); const fd = new FormData($form); fd.set('telegram', tgVal ? ('@' + tgVal) : ''); const oldHtml = $btn.innerHTML; $btn.disabled = true; $btn.innerHTML = 'Gönderiliyor…'; try { const res = await fetch('/ajax/contact.php', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: fd, credentials: 'same-origin' }); const data = await res.json().catch(() => ({})); if (!res.ok || !data.success) { const msg = (data && (data.error_human || data.error)) ? (data.error_human || data.error) : ('HTTP ' + res.status); if (res.status === 429 && data.retry_after) { ghShowToastIn('ghToastMain','err', data.error_human || ('Please wait '+data.retry_after+'s')); } else { ghShowToastIn('ghToastMain','err', msg || 'Send error'); } if (msg === 'Name required') markError($name, 'Name required'); if (msg === 'Invalid email') markError($email, 'Invalid email'); if (msg === 'Invalid telegram') markError($tg, 'Invalid telegram'); if (msg === 'Invalid phone') markError($whats, 'Invalid phone'); return; } ghShowToastIn('ghToastMain','ok','Talebiniz başarıyla gönderildi. En kısa sürede sizinle iletişime geçeceğiz.'); $form.reset(); if ($waFull) $waFull.value = ''; if ($channel) $channel.value = 'email'; if ($ccode) $ccode.value = '+'; document.querySelectorAll('.gh-optional').forEach(w=>w.classList.remove('is-active')); } catch (err) { ghShowToastIn('ghToastMain','err','Network error. Try again later.'); } finally { $btn.disabled = false; $btn.innerHTML = oldHtml; } }); }); </script> </main> <style> /* =================== GAMBLEHUB FOOTER =================== */ .gh-footer { position: relative; border-top: 1px solid rgba(255, 255, 255, 0.10); background: linear-gradient( 180deg, rgba(7, 13, 25, 0.82) 0%, rgba(5, 10, 20, 0.96) 100% ); } .gh-footer::before { content: ""; position: absolute; top: -1px; left: 50%; width: min(34rem, 72%); height: 1px; transform: translateX(-50%); pointer-events: none; background: linear-gradient( 90deg, transparent, rgba(255, 255, 255, 0.16), transparent ); } .gh-footer-inner { width: 100%; max-width: 80rem; margin: 0 auto; padding: 2.5rem 1rem 1.25rem; } /* =================== ОСНОВНОЙ БЛОК =================== */ .gh-footer-main { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2rem; } /* =================== ЛОГО И ОПИСАНИЕ =================== */ .gh-footer-brand { min-width: 0; } .gh-footer-logo { display: inline-flex; align-items: center; max-width: 100%; text-decoration: none; } .gh-footer-logo img { display: block; width: 150px; height: auto; max-width: 100%; } .gh-footer-slogan { max-width: 27rem; margin: 1rem 0 0; color: #8491a4; font-size: 0.8125rem; line-height: 1.65; } /* =================== МЕНЮ =================== */ .gh-footer-menus { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem; min-width: 0; } .gh-footer-column { min-width: 0; } .gh-footer-title { margin: 0 0 0.875rem; color: #e2e8f0; font-size: 0.875rem; line-height: 1.35; font-weight: 700; } /* * Нормализация HTML из BottomMenu() */ .gh-footer-menu, .gh-footer-menu ul, .gh-footer-menu ol { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; width: 100%; min-width: 0; margin: 0; padding: 0; list-style: none; } .gh-footer-menu li { width: 100%; min-width: 0; margin: 0; padding: 0; list-style: none; } .gh-footer-menu a { position: relative; display: inline-flex; align-items: center; max-width: 100%; color: #94a3b8; font-size: 0.8125rem; line-height: 1.5; text-decoration: none !important; overflow-wrap: anywhere; transition: color 0.2s ease; } .gh-footer-menu a:hover { color: #e7fbff; } /* Активный пункт меню */ .gh-footer-menu .gh-bottom-menu-link.is-active { color: #e7fbff; } .gh-footer-menu .gh-bottom-menu-link.is-active::after { content: ""; position: absolute; right: 0; bottom: -3px; left: 0; height: 1px; border-radius: 999px; background: rgba(231, 251, 255, 0.68); pointer-events: none; } /* =================== НИЖНЯЯ СТРОКА =================== */ .gh-footer-bottom { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; margin-top: 2rem; padding-top: 1.25rem; border-top: 1px solid rgba(255, 255, 255, 0.075); } .gh-footer-copy, .gh-footer-created { margin: 0; color: #64748b; font-size: 0.75rem; line-height: 1.55; text-align: center; } .gh-footer-created strong { color: #a9b5c5; font-weight: 600; } /* =================== УЗКАЯ МОБИЛКА =================== */ @media (max-width: 420px) { .gh-footer-inner { padding-top: 2rem; } .gh-footer-main { gap: 1.75rem; } .gh-footer-menus { grid-template-columns: minmax(0, 1fr); gap: 1.75rem; } .gh-footer-logo img { width: 145px; } .gh-footer-slogan { margin-top: 0.875rem; font-size: 0.8125rem; } .gh-footer-title { margin-bottom: 0.75rem; font-size: 0.9375rem; } .gh-footer-menu a { font-size: 0.8125rem; } } /* =================== ПЛАНШЕТ =================== */ @media (min-width: 640px) { .gh-footer-inner { padding-right: 1.5rem; padding-left: 1.5rem; } .gh-footer-main { grid-template-columns: minmax(220px, 0.8fr) minmax(0, 1.2fr); align-items: start; gap: 3rem; } .gh-footer-menus { gap: 2.5rem; } .gh-footer-bottom { flex-direction: row; justify-content: space-between; gap: 1rem; } .gh-footer-copy { text-align: left; } .gh-footer-created { text-align: right; } } /* =================== ДЕСКТОП =================== */ @media (min-width: 1024px) { .gh-footer-inner { padding: 2.75rem 2rem 1.25rem; } .gh-footer-main { grid-template-columns: minmax(280px, 0.85fr) minmax(0, 1.15fr); gap: 4.5rem; } .gh-footer-brand { padding-right: 3rem; border-right: 1px solid rgba(255, 255, 255, 0.075); } .gh-footer-menus { gap: 4rem; } } /* =================== RTL =================== */ [dir="rtl"] .gh-footer-menu, [dir="rtl"] .gh-footer-menu ul, [dir="rtl"] .gh-footer-menu ol { align-items: flex-start; } @media (min-width: 640px) { [dir="rtl"] .gh-footer-copy { text-align: right; } [dir="rtl"] .gh-footer-created { text-align: left; } } @media (min-width: 1024px) { [dir="rtl"] .gh-footer-brand { padding-right: 0; padding-left: 3rem; border-right: 0; border-left: 1px solid rgba(255, 255, 255, 0.075); } } @media (prefers-reduced-motion: reduce) { .gh-footer-menu a { transition: none; } } </style> <!-- FOOTER --> <footer class="gh-footer"> <div class="gh-footer-inner"> <div class="gh-footer-main"> <!-- ЛОГО И ОПИСАНИЕ --> <div class="gh-footer-brand"> <a href="/tr/" class="gh-footer-logo" aria-label="GambleHub" > <img src="/images/logo.svg" width="150" height="29" alt="GambleHub" loading="lazy" decoding="async" > </a> <p class="gh-footer-slogan"> GambleHub; slotlar, bulmacalar, beceri oyunları ve sanal sporlar dahil geniş bir oyun yelpazesi sunar. </p> </div> <!-- МЕНЮ --> <div class="gh-footer-menus"> <!-- COMPANY --> <div class="gh-footer-column"> <div class="gh-footer-title"> Şirket </div> <div class="gh-footer-menu"> <a href="/tr/about-us/" class="gh-bottom-menu-link">Bizim hakkımızda</a><a href="/tr/services/" class="gh-bottom-menu-link">Hizmetler</a><a href="/tr/features/" class="gh-bottom-menu-link">Fırsatlar</a><a href="/tr/partners/" class="gh-bottom-menu-link">Ortaklar</a><a href="/tr/contact-us/" class="gh-bottom-menu-link">Iletişim bilgileri</a> </div> </div> <!-- LEGAL --> <div class="gh-footer-column"> <div class="gh-footer-title"> Yasal bilgiler </div> <div class="gh-footer-menu"> <a href="/tr/responsible-gaming/" class="gh-bottom-menu-link">Sorumlu oyun</a><a href="/tr/gaming-license/" class="gh-bottom-menu-link">Oyun lisansı</a><a href="/tr/privacy-policy/" class="gh-bottom-menu-link">Gizlilik Bildirimi</a><a href="/tr/terms-and-conditions/" class="gh-bottom-menu-link">Kullanım şartları</a><a href="/tr/cookie-policy/" class="gh-bottom-menu-link">Çerez politikası</a> </div> </div> </div> </div> <!-- НИЖНЯЯ СТРОКА --> <div class="gh-footer-bottom dir-ltr"> <p class="gh-footer-copy"> Copyright © GambleHub. 2024 ® All Rights Reserved. </p> <p class="gh-footer-created"> Created by <strong>GambleHub</strong> </p> </div> </div> </footer> <!-- Единственный JS: мобильное меню --> <script> (function(){ const btn = document.getElementById('navToggle'); const panel = document.getElementById('navPanel'); if(!btn || !panel) return; // БЕЗ блокировки фона — ничего не трогаем у <html> const isOpen = () => panel.classList.contains('active'); const open = () => panel.classList.add('active'); const close = () => panel.classList.remove('active'); // Бургер — переключение btn.addEventListener('click', (e)=>{ e.stopPropagation(); isOpen() ? close() : open(); }); // Клик внутри меню — не закрывает panel.addEventListener('click', e => e.stopPropagation()); // Клик вне меню/кнопки — закрывает document.addEventListener('click', (e)=>{ if (isOpen() && !panel.contains(e.target) && !btn.contains(e.target)) close(); }); // Esc — закрыть document.addEventListener('keydown', (e)=>{ if (e.key === 'Escape' && isOpen()) close(); }); })(); </script> <script> (function(){ const RECENT_LIMIT = 6; const STORE_KEY = 'lang_recent_codes'; function uid(){ return Math.random().toString(36).slice(2,8); } function readRecent(){ try { return JSON.parse(localStorage.getItem(STORE_KEY) || '[]'); } catch(e){ return []; } } function pushRecent(code){ const arr = readRecent().filter(c=>c!==code); arr.unshift(code); localStorage.setItem(STORE_KEY, JSON.stringify(arr.slice(0, RECENT_LIMIT))); } function closeAll(except){ document.querySelectorAll('.language-menu.show').forEach(m=>{ if (m!==except) m.classList.remove('show'); }); } // Делегирование document.addEventListener('click', (e)=>{ const wrap = e.target.closest('.language-selector'); const isBtn = e.target.closest('.language-button'); if (wrap && isBtn){ const menu = wrap.querySelector('.language-menu'); if (!menu) return; closeAll(menu); menu.classList.toggle('show'); const input = menu.querySelector('.language-search input'); if (menu.classList.contains('show') && input){ setTimeout(()=>input.focus(), 0); } return; } // Выбор языка const link = e.target.closest('.language-menu a[data-lang]'); if (link){ const code = link.getAttribute('data-lang'); if (code) pushRecent(code); // стандартный переход по href произойдет сам return; } // Клик вне — закрыть if (!e.target.closest('.language-menu')) closeAll(); }); // Клавиатура: Esc/стрелки/Home/End/Enter/Space document.addEventListener('keydown', (e)=>{ const openMenu = document.querySelector('.language-menu.show'); if (!openMenu) return; const focusables = [...openMenu.querySelectorAll('a[data-lang]')]; const idx = focusables.indexOf(document.activeElement); if (e.key === 'Escape'){ closeAll(); return; } if (e.key === 'ArrowDown'){ e.preventDefault(); (focusables[idx+1] || focusables[0])?.focus(); } if (e.key === 'ArrowUp'){ e.preventDefault(); (focusables[idx-1] || focusables[focusables.length-1])?.focus(); } if (e.key === 'Home'){ e.preventDefault(); focusables[0]?.focus(); } if (e.key === 'End'){ e.preventDefault(); focusables[focusables.length-1]?.focus(); } if (e.key === ' ' || e.key === 'Enter'){ if (document.activeElement?.tagName === 'A'){ document.activeElement.click(); } } }); // Живой поиск document.addEventListener('input', (e)=>{ const input = e.target.closest('.language-search input'); if (!input) return; const menu = input.closest('.language-menu'); if (!menu) return; const q = input.value.trim().toLowerCase(); menu.querySelectorAll('a[data-lang]').forEach(a=>{ const hay = (a.dataset.search || '').toLowerCase(); a.style.display = hay.includes(q) ? '' : 'none'; }); }); // Рендер «Недавние» (когда меню впервые открывается) document.addEventListener('click', (e)=>{ const btn = e.target.closest('.language-button'); if (!btn) return; const menu = btn.parentElement.querySelector('.language-menu'); if (!menu) return; const recentWrap = menu.querySelector('[data-recent]'); if (!recentWrap) return; if (recentWrap.dataset.inited === '1') return; const recent = readRecent(); if (!recent.length){ recentWrap.remove(); return; } // Показать только те, что реально есть в меню const map = {}; menu.querySelectorAll('a[data-lang]').forEach(a=>{ map[a.dataset.lang] = a.outerHTML; }); const inner = recent.map(code=>map[code]).filter(Boolean).join(''); if (inner){ recentWrap.insertAdjacentHTML('beforeend', inner); recentWrap.dataset.inited = '1'; } else { recentWrap.remove(); } }); })(); </script> <script> (function(){ const pillsWrap = document.querySelector('.filter-pills'); const gridWrap = document.getElementById('providersGrid'); if (!pillsWrap || !gridWrap) return; const pills = Array.from(pillsWrap.querySelectorAll('[data-cat]')); const px = (gridWrap.getAttribute('data-px') || '').trim(); const url = (gridWrap.getAttribute('data-url') || '').trim(); let current = 9; let inflight; // ---------- helpers ---------- function setActive(catId){ pills.forEach(btn => { const on = parseInt(btn.dataset.cat,10) === catId; btn.classList.toggle('is-active', on); btn.setAttribute('aria-selected', on ? 'true' : 'false'); btn.setAttribute('role', 'tab'); }); } function gridColsClass(){ return 'providers-grid mt-6 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3'; } // Берём ТОЛЬКО основной грид — прямой ребёнок providersGrid function getMainGrid(){ // :scope гарантирует, что не схватим UL внутри модалки return gridWrap.querySelector(':scope > ul.providers-grid'); } // скелетон — только внутри основного UL function renderSkeleton(count = 8){ const items = Array.from({length: count}) .map(() => '<li class="gh-skel gh-skel-card"></li>') .join(''); let ul = getMainGrid(); if (!ul) { ul = document.createElement('ul'); ul.className = gridColsClass(); gridWrap.appendChild(ul); } ul.innerHTML = items; } function ensureOverlay(){ let ov = gridWrap.querySelector('.gh-preload__overlay'); if (!ov) { ov = document.createElement('div'); ov.className = 'gh-preload__overlay'; ov.setAttribute('aria-hidden', 'true'); ov.innerHTML = '<div class="gh-spinner" role="progressbar" aria-label="Loading"></div>'; gridWrap.appendChild(ov); } return ov; } async function loadCat(catId){ if (inflight) inflight.abort(); inflight = new AbortController(); setActive(catId); gridWrap.setAttribute('aria-busy','true'); renderSkeleton(8); const overlay = ensureOverlay(); overlay.classList.add('is-visible'); const started = performance.now(); const minShow = 450; try{ const body = new URLSearchParams({ id: String(catId), px, url}); const resp = await fetch('/ajax/providers.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, credentials: 'same-origin', body, signal: inflight.signal }); if(!resp.ok) throw new Error('Network error'); const html = await resp.text(); const elapsed = performance.now() - started; if (elapsed < minShow) { await new Promise(r => setTimeout(r, minShow - elapsed)); } // Меняем только основной UL, модалку не трогаем const oldGrid = getMainGrid(); if (oldGrid) { // html = <ul class="providers-grid ...">...</ul> oldGrid.outerHTML = html; } else { // на всякий случай, если по каким-то причинам UL ещё нет gridWrap.insertAdjacentHTML('beforeend', html); } } catch(err){ if (err.name === 'AbortError') return; console.error(err); gridWrap.innerHTML = ` <div class="mt-10 text-center text-slate-300"> <div class="inline-block border border-white/10 rounded-2xl px-6 py-8 bg-white/5 max-w-md"> <p class="text-lg font-semibold text-white"> Ошибка загрузки </p> <p class="text-sm mt-1 text-slate-400"> Пожалуйста, попробуйте ещё раз </p> </div> </div>`; } finally { gridWrap.removeAttribute('aria-busy'); const overlay = gridWrap.querySelector('.gh-preload__overlay'); if (overlay) overlay.classList.remove('is-visible'); } } // ---------- events ---------- pillsWrap.addEventListener('click', (e) => { const btn = e.target.closest('[data-cat]'); if (!btn) return; const catId = parseInt(btn.dataset.cat, 10); if (catId === current) return; current = catId; loadCat(current); }); setActive(current); })(); </script> <script> document.addEventListener('DOMContentLoaded', () => { const io = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('is-inview'); obs.unobserve(entry.target); } }); }, { threshold: 0.3 }); document.querySelectorAll('[data-reveal] .reveal-item').forEach(el => io.observe(el)); }); </script> <script> // === Smooth scroll with offset (GambleHub style) === document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { const targetId = this.getAttribute('href').substring(1); const target = document.getElementById(targetId); if (target) { e.preventDefault(); // Смещение в пикселях (можно менять) const offset = 70; // Расчёт позиции с учётом текущего скролла const y = target.getBoundingClientRect().top + window.scrollY - offset; // Плавный скролл window.scrollTo({ top: y, behavior: 'smooth' }); // Добавим лёгкую подсветку цели (опционально) target.classList.add('ring-1', 'ring-aqua-400/40', 'transition'); setTimeout(() => target.classList.remove('ring-1', 'ring-aqua-400/40'), 1000); } }); }); </script> <script> document.querySelectorAll('a, button').forEach(el => { el.addEventListener('click', () => { if (typeof gtag === 'function') { gtag('event', 'click', { event_category: 'interaction', event_label: el.dataset.role || el.dataset.href || 'unknown', value: 1 }); } }); }); </script> <!-- ===================== VIDEO MODAL ===================== --> <div id="ghVideoModal" class="fixed inset-0 z-[90] hidden items-center justify-center p-4 bg-black/70 backdrop-blur-sm transition-opacity duration-300 opacity-0"> <!-- overlay закрытия --> <div class="absolute inset-0" onclick="ghCloseVideoModal()"></div> <!-- content --> <div id="ghVideoModalContent" class="relative w-full max-w-md rounded-3xl bg-night-900 border border-white/10 p-8 gh-glow text-center transform scale-95 opacity-0 transition-all duration-300 ease-out"> <!-- YouTube icon --> <div class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-3xl bg-red-600/20 text-red-400 shadow-[0_0_30px_rgba(255,0,0,.25)]"> <svg xmlns="http://www.w3.org/2000/svg" width="42" height="42" fill="currentColor" viewBox="0 0 16 16"> <path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.01 2.01 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.01 2.01 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31 31 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.01 2.01 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A100 100 0 0 1 7.858 2zM6.4 5.209v4.818l4.157-2.408z"/> </svg> </div> <!-- Title --> <h2 class="text-lg font-semibold text-white mb-2"> Video yakında güncellenecek </h2> <!-- Text --> <p class="text-slate-400 text-sm leading-relaxed mb-6"> Video şu anda güncelleniyor.<br>Lütfen daha sonra tekrar kontrol edin — yeni sürüm yolda. </p> <!-- Close button --> <button onclick="ghCloseVideoModal()" class="mt-1 w-full rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-slate-200 hover:border-white/20 hover:bg-white/10 transition"> Kapat </button> </div> </div> <!-- ===================== / VIDEO MODAL ===================== --> <script> function ghOpenVideoModal() { const modal = document.getElementById('ghVideoModal'); const content = document.getElementById('ghVideoModalContent'); modal.classList.remove('hidden'); modal.classList.add('flex'); // блокируем скролл document.body.style.overflow = "hidden"; // запускаем анимацию (fade + scale) setTimeout(() => { modal.classList.remove('opacity-0'); content.classList.remove('opacity-0', 'scale-95'); }, 20); } function ghCloseVideoModal() { const modal = document.getElementById('ghVideoModal'); const content = document.getElementById('ghVideoModalContent'); // анимация скрытия modal.classList.add('opacity-0'); content.classList.add('opacity-0', 'scale-95'); // ждём 300ms пока анимация пойдёт setTimeout(() => { modal.classList.add('hidden'); modal.classList.remove('flex'); document.body.style.overflow = ""; }, 300); } </script> <!-- ===================== SOCIAL MODAL ===================== --> <div id="ghSocialModal" class="fixed inset-0 z-[90] hidden items-center justify-center p-4 bg-black/70 backdrop-blur-sm transition-opacity duration-300 opacity-0"> <!-- overlay --> <div class="absolute inset-0" onclick="ghCloseSocialModal()"></div> <!-- content --> <div id="ghSocialModalContent" class="relative w-full max-w-md rounded-3xl bg-night-900 border border-white/10 p-8 gh-glow text-center transform scale-95 opacity-0 transition-all duration-300 ease-out"> <!-- icons row — теперь компактный, как YouTube-иконка по смыслу --> <div class="flex items-center justify-center gap-3 mb-6"> <div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-white/5 text-slate-200 shadow-[0_0_18px_rgba(255,255,255,.08)]"> <!-- X --> <svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <path d="M3 3l18 18M21 3L3 21" stroke="currentColor" stroke-width="1.6"/> <path d="M14.5 3h3.9L12.9 11l6.1 10h-3.9L12 14.6 8.9 21H5l6.1-10L5 3h3.9L12 9.4 14.5 3z" fill="currentColor" opacity=".9"/> </svg> </div> <div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-white/5 text-slate-200 shadow-[0_0_18px_rgba(255,255,255,.08)]"> <!-- LinkedIn --> <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <path d="M4.98 3.5C4.98 4.88 3.86 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1s2.48 1.12 2.48 2.5zM0 8.5h5V23H0V8.5zM8 8.5h4.8v2h.07c.67-1.2 2.3-2.46 4.73-2.46C21.86 8.04 24 10 24 13.86V23h-5v-7.7c0-1.84-.03-4.2-2.56-4.2-2.56 0-2.96 2-2.96 4.06V23H8V8.5z"/> </svg> </div> <div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-white/5 text-slate-200 shadow-[0_0_18px_rgba(255,255,255,.08)]"> <!-- YouTube --> <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> <path d="M23.5 6.2a3.4 3.4 0 0 0-2.4-2.4C19.2 3.3 12 3.3 12 3.3s-7.2 0-9.1.5A3.4 3.4 0 0 0 .5 6.2C0 8.1 0 12 0 12s0 3.9.5 5.8a3.4 3.4 0 0 0 2.4 2.4c1.9.5 9.1.5 9.1.5s7.2 0 9.1-.5a3.4 3.4 0 0 0 2.4-2.4c.5-1.9.5-5.8.5-5.8s0-3.9-.5-5.8ZM9.6 15.5v-7l6.2 3.5-6.2 3.5Z"/> </svg> </div> </div> <!-- Title --> <h2 class="text-lg font-semibold text-white mb-3"> Şu anda projelerle çok meşgulüz </h2> <!-- Text --> <p class="text-slate-400 text-sm leading-relaxed mb-6"> Şu anda tüm odağımız, ürünlerinizin geliştirilmesi ve entegrasyonu üzerinde.<br><br>Yine de sosyal medya için zaman ayırmaya çalışıyoruz. Çok yakında yeni içerikler eklenecek — bizi takipte kalın! </p> <!-- Close button --> <button onclick="ghCloseSocialModal()" class="mt-1 w-full rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-slate-200 hover:border-white/20 hover:bg-white/10 transition"> Kapat </button> </div> </div> <!-- ===================== / SOCIAL MODAL ===================== --> <script> function ghOpenSocialModal() { const modal = document.getElementById('ghSocialModal'); const content = document.getElementById('ghSocialModalContent'); modal.classList.remove('hidden'); modal.classList.add('flex'); document.body.style.overflow = "hidden"; setTimeout(() => { modal.classList.remove('opacity-0'); content.classList.remove('opacity-0', 'scale-95'); }, 20); } function ghCloseSocialModal() { const modal = document.getElementById('ghSocialModal'); const content = document.getElementById('ghSocialModalContent'); modal.classList.add('opacity-0'); content.classList.add('opacity-0', 'scale-95'); setTimeout(() => { modal.classList.add('hidden'); modal.classList.remove('flex'); document.body.style.overflow = ""; }, 300); } </script> </body> </html>