• 08-08-2026, 05:41:17
    #1
    tasarımı söylemiyorum tasarım berbat zaten uğraşmadım sadece dakikada bir yenileyin sayfayı

  • Kabul Edilen Cevap
    • 1 Beğeni
      UbrTrade adlı üyeden alıntı: mesajı görüntüle
      ama beğendim kullanmak isterim paylaşırsanız sonuçta sizinki daha güzel olmuş
      Elbette hocam, elimden geldiğince yorum satırlarıyla ne nedir, ne işe yarar anlatmaya çalıştım, spoilerden kodu görebilirsiniz

      <?php
      /**
       * ARCADE GERI SAYIM SAYACI  —  animasyonlu GIF
       * ---------------------------------------------------------------
       *
       * NEDEN GIF?
       * PNG durgun bir resimdir; sayfa yenilenmeden degismez. GIF ise
       * animasyon tasiyabildigi icin, sunucu istek geldigi anda 60
       * saniyelik bir animasyon uretir ve tarayici bunu kendi oynatir.
       * Sonuc: sayac sayfa yenilenmeden tiklar. Forumlar <img> etiketine
       * izin verdigi icin de konu icinde, imzada, her yerde calisir.
       *
       * KULLANIM
       *   HTML   : <img src="https://siteniz.com/index.php">
       *   BBCode : [img]https://siteniz.com/index.php[/img]
       *
       * TEMALAR (adresin sonuna eklenir)
       *   ?tema=koyu     koyu zemin (varsayilan)
       *   ?tema=acik     acik zemin
       *   ?tema=seffaf   zemin saydam; forum kendi arka planini gosterir,
       *                  yani hem koyu hem acik temali forumda uyar
       *
       * KURULUM
       *   1) Bu dosyayi sunucuya atin.
       *   2) Yanina "onbellek" adinda bos bir klasor acip yazma izni
       *      verin (755). Acmazsaniz yine calisir, sadece her istekte
       *      bastan uretir - yani daha yavas olur.
       *   3) Turkce karakter (GUN yerine GÜN) isterseniz ayni klasore
       *      "font.ttf" atin. Rakamlar fonta ihtiyac duymaz, onlari kod
       *      kendi cizer; font yalnizca alt etiketler icindir.
       *
       * GEREKSINIM
       *   PHP + GD eklentisi (imagegif). Baska kutuphane yok.
       *
       * NOT
       *   Animasyon 60 saniyeliktir; sonunda son karede durur. Sayfa
       *   yenilendiginde bastan uretilir. Daha uzun sursun isterseniz
       *   asagidaki $KARE_SAYISI degerini buyutun (dosya boyutu artar).
       */
      // ==================================================================
      // AYARLAR  —  duzenlemeniz gereken tek bolum burasi
      // ==================================================================
      date_default_timezone_set('Europe/Istanbul');
      // Geri sayimin bitecegi an. Bicim: 'YIL-AY-GUN SAAT:DAKIKA:SANIYE'
      $HEDEF       = '2026-09-15 14:13:00';
      // Sayacin ustundeki tabelada yazan metin
      $BASLIK      = 'UGILABS - GIF Versiyon';
      // Sure doldugunda ekranda yazacak metin
      $BITTI_METNI = 'OYUN BITTI';
      // ------------------------------------------------------------------
      // ANIMASYON
      // ------------------------------------------------------------------
      // Kac saniyelik animasyon uretilsin. 60 = 1 dakika.
      // Buyutursen daha uzun sure tiklar ama dosya buyur:
      //   60 kare  ~75 KB  (1 dakika)
      //  300 kare ~250 KB  (5 dakika)
      $KARE_SAYISI  = 60;
      // Her karenin ekranda kalma suresi (1/100 saniye cinsinden).
      // 100 = 1 saniye. Sayac saniye saydigi icin bunu degistirme.
      $KARE_SURESI  = 100;
      // 1 = bir kez oynar, sonra son karede durur.
      // 0 = sonsuz doner. Surekli hareket eder ama her turda basa
      //     saracagi icin yanlis zaman gostermeye baslar. Sayac icin
      //     1 dogrusu; sonsuz dongu ancak susleme amacli isine yarar.
      $DONGU        = 1;
      // false yaparsan saniye hanesi kaybolur, sadece gun/saat/dakika
      // kalir. O durumda animasyona gerek kalmaz, tek kare uretilir.
      $SANIYE_GOSTER = true;
      // ------------------------------------------------------------------
      // ONBELLEK  —  paylasimli hostingde sunucuyu ayakta tutar
      //
      // Her istekte animasyonu bastan cizmek sunucuyu yorar (~0.8 sn).
      // Populer bir konuda 50 kisi ayni anda sayfayi acarsa bu 40 saniye
      // islemci demektir ve paylasimli hosting bunu kaldirmaz.
      //
      // Bu yuzden uretilen GIF diske yazilir; asagidaki sure boyunca
      // gelen istekler yeniden cizmeden hazir dosyayi alir. 50 kisi ayni
      // anda girse bile tek uretim yapilir.
      //
      // Sayacin dogrulugunu bozmaz: en fazla bu sure kadar geriden gelir,
      // yani 3 saniyede kimsenin fark edecegi bir sapma olusmaz.
      // ------------------------------------------------------------------
      // Uretilen dosya kac saniye saklansin. 0 yazarsan onbellek kapanir.
      $ONBELLEK_SN  = 3;
      // Dosyalarin yazilacagi klasor. Yazma izni yoksa sistem yine
      // calisir, sadece her istekte bastan uretir.
      $ONBELLEK_DIZ = __DIR__ . '/onbellek';
      // ------------------------------------------------------------------
      // FONT
      // Rakamlar fonta ihtiyac duymaz - onlari kod nokta nokta cizer.
      // Buradaki font yalnizca alt etiketler (GUN, SAAT...) ve baslik
      // icindir. Turkce karakter isterseniz klasore font.ttf atin;
      // bulunamazsa asagidaki sistem fontlari sirayla denenir.
      // ------------------------------------------------------------------
      $FONT_ADAYLARI = [
          __DIR__ . '/font.ttf',
          'C:/Windows/Fonts/consolab.ttf',
          '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
          '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
      ];
      // ------------------------------------------------------------------
      // TEMA  —  arcade dolabinin ekrani
      // ------------------------------------------------------------------
      $tema = isset($_GET['tema']) ? strtolower($_GET['tema']) : 'koyu';
      if (!in_array($tema, ['koyu', 'acik', 'seffaf'], true)) { $tema = 'koyu'; }
      $T = [
          // Ortak arcade paleti - zeminden bagimsiz, her iki temada da okunur.
          'siyan'    => [ 34, 224, 232],
          'magenta'  => [255,  61, 139],
          'amber'    => [255, 197,  61],
          'mor'      => [ 27,  16,  53],
          'ten'      => [217, 142,  95],
          'tulum'    => [ 46,  92, 138],
          'tugla'    => [166,  74,  58],
          'tuglaKen' => [ 92,  38,  30],
      ];
      // 'ekran' her temada koyu kalir - CRT camı zeminden bagimsizdir.
      // 'baslik'  : ekranin ICINDEKI etiketler (hep koyu zemin uzerinde)
      // 'marquee' : ekranin USTUNDEKI tabela yazisi (zemine gore degisir)
      if ($tema === 'acik') {
          $T['zemin']   = [232, 230, 240];
          $T['ekran']   = [ 38,  24,  70];
          $T['cerceve'] = [ 62,  44, 104];
          $T['baslik']  = [166, 152, 208];
          $T['marquee'] = [ 44,  30,  84];   // acik zeminde koyu yazi
          $T['golge']   = [206, 202, 220];
      } else {
          $T['zemin']   = [ 16,  10,  30];
          $T['ekran']   = [ 30,  18,  58];
          $T['cerceve'] = [ 62,  44, 104];
          $T['baslik']  = [148, 132, 196];
          $T['marquee'] = [172, 156, 216];
          $T['golge']   = [ 10,   6,  20];
      }
      // ==================================================================
      // ONBELLEKTEN SUNMA
      //
      // Bu bolum dosyanin en basinda duruyor cunku amaci cizim islerine
      // hic girmeden isi bitirmek. Yeterince taze bir dosya varsa onu
      // gonderip cikiyoruz; asagidaki kodun tamami hic calismiyor.
      // ==================================================================
      /** Hazir GIF verisini tarayiciya gonderir ve betigi sonlandirir. */
      function gifGonder($veri)
      {
          header('Content-Type: image/gif');
          header('Content-Length: ' . strlen($veri));
          // Tarayiciya "bu resmi saklama" diyoruz. Saklarsa sayfa
          // yenilendiginde eski animasyonu gosterir ve sayac yanlis
          // zamanda kalir.
          header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
          header('Pragma: no-cache');
          header('Expires: Sat, 01 Jan 2000 00:00:00 GMT');
          echo $veri;
          exit;
      }
      $onbellekDosya = null;
      if ($ONBELLEK_SN > 0) {
          // Dosya adini ayarlardan uretiyoruz: ayni ayarlar ayni dosyayi
          // kullanir, farkli tema kendi dosyasini alir. Boylece temalar
          // birbirine karismaz.
          $anahtar = md5(implode('|', [
              $HEDEF, $BASLIK, $BITTI_METNI, $tema,
              $KARE_SAYISI, $KARE_SURESI, $DONGU, $SANIYE_GOSTER,
          ]));
          if (!is_dir($ONBELLEK_DIZ)) { @mkdir($ONBELLEK_DIZ, 0755, true); }
          $onbellekDosya = $ONBELLEK_DIZ . '/' . $anahtar . '.gif';
          // Dosya var ve yeterince taze mi? Oyleyse hic cizmeden gonder.
          if (is_readable($onbellekDosya)) {
              $yas = time() - (int) @filemtime($onbellekDosya);
              if ($yas >= 0 && $yas < $ONBELLEK_SN) {
                  $hazir = @file_get_contents($onbellekDosya);
                  if ($hazir !== false && strlen($hazir) > 0) {
                      gifGonder($hazir);
                  }
              }
          }
          // Buraya dustuyse dosya yok ya da eskimis; asagida yeniden
          // uretilip en sonda diske yazilacak.
      }
      // ==================================================================
      // HAZIRLIK
      // ==================================================================
      $FONT = null;
      foreach ($FONT_ADAYLARI as $f) {
          if (is_readable($f)) { $FONT = $f; break; }
      }
      $ttf = ($FONT !== null);
      $hedefTs = strtotime($HEDEF);
      $simdi   = time();
      if (!$SANIYE_GOSTER) { $KARE_SAYISI = 1; }
      $ETIKETLER = ['GUN', 'SAAT', 'DAKIKA', 'SANIYE'];
      $ADET      = $SANIYE_GOSTER ? 4 : 3;
      // ------------------------------------------------------------------
      // OLCULER
      // Ekran bir arcade dolabinin CRT'si: disinda cerceve, icinde rakamlar,
      // en altta Ralph'in yumruk attigi tugla siras var.
      // ------------------------------------------------------------------
      $PADDING   = 14;   // dis cerceve kalinligi
      // Rakamlar 5x7 glif izgarasi. $PIX bir glif pikselinin blok boyutu.
      $PIX       = 6;
      $HANE_G    = 5 * $PIX;          // bir rakam hanesinin genisligi
      $RAKAM_YUK = 7 * $PIX;          // glif yuksekligi
      $HANE_BOS  = $PIX;              // ayni grup icindeki haneler arasi
      $GRUP_BOS  = $PIX * 3;          // gruplar arasi (iki nokta buraya gelir)
      // Gun hanesi 3 basamak olabilir (999 gun), digerleri hep 2 basamak.
      $gunBasamak = max(2, strlen((string) (int) floor(max(0, $hedefTs - $simdi) / 86400)));
      $BASAMAKLAR = [$gunBasamak, 2, 2, 2];
      $BASAMAKLAR = array_slice($BASAMAKLAR, 0, $ADET);
      $grupGen = [];
      foreach ($BASAMAKLAR as $n) {
          $grupGen[] = $n * $HANE_G + ($n - 1) * $HANE_BOS;
      }
      // Rakam bloklarinin toplam genisligi
      $RAKAM_G = array_sum($grupGen) + ($ADET - 1) * $GRUP_BOS;
      // Ralph rakamlarin SAGINDA kendi seridinde durur - etiketleri kapatmaz.
      $RALPH_P   = 3;                  // Ralph'in piksel blok boyutu
      $RALPH_G   = 13 * $RALPH_P;      // Ralph + yumruk icin ayrilan genislik
      $RALPH_BOS = 14;                 // rakamlarla Ralph arasi bosluk
      $EKRAN_G = $RAKAM_G + $RALPH_BOS + $RALPH_G + 16;
      $EKRAN_SOL = $PADDING;
      $EKRAN_UST = 42;                       // ustte baslik seridi
      $EKRAN_YUK = 92;                       // rakamlar + etiketler
      $TUGLA_YUK = 26;                       // Ralph'in kirdigi tugla sirasi
      $W = $EKRAN_G + $PADDING * 2;
      $H = $EKRAN_UST + $EKRAN_YUK + $TUGLA_YUK + $PADDING;
      // Rakamlarin dikey konumu ($RAKAM_YUK yukarida glif izgarasindan geldi)
      $RAKAM_UST = $EKRAN_UST + 16;
      $ETIKET_Y  = $RAKAM_UST + $RAKAM_YUK + 16;
      // ------------------------------------------------------------------
      // SEFFAF TEMA ICIN ISARET RENGI
      //
      // GIF'te saydamlik, "su renk saydam sayilsin" diye tek bir rengi
      // isaretleyerek olur. Dogrudan saydam cizemeyiz, cunku GIF'e
      // cevirirken kullanilan imagetruecolortopalette() saydamlik
      // bilgisini korumaz.
      //
      // Bu yuzden zemini once bu renkle boyuyoruz - tasarimda hic
      // gecmeyen, kimseye benzemeyen parlak bir ton. GIF'e yazarken de
      // "bu renk saydamdir" diye isaretliyoruz. Sonuc: forumun kendi
      // arka plani resmin altindan gorunuyor.
      // ------------------------------------------------------------------
      define('SEFFAF_R', 255);
      define('SEFFAF_G', 0);
      define('SEFFAF_B', 254);
      // ==================================================================
      // ARCADE PIKSEL FONT
      // Rakamlar 5x7 izgaraya cizilmis gercek bitmap glifler. Her "piksel"
      // ekranda $p x $p'lik bir blok olur - klasik arcade skor tabelasi gibi.
      // TTF'e bagli degil, her sunucuda birebir ayni gorunur.
      // ==================================================================
      /** 5x7 rakam glifleri. Her satir 5 karakter: '#' dolu, '.' bos. */
      function glif($ch)
      {
          static $font = [
              '0' => ['.###.',
                      '#...#',
                      '#..##',
                      '#.#.#',
                      '##..#',
                      '#...#',
                      '.###.'],
              '1' => ['..#..',
                      '.##..',
                      '..#..',
                      '..#..',
                      '..#..',
                      '..#..',
                      '.###.'],
              '2' => ['.###.',
                      '#...#',
                      '....#',
                      '...#.',
                      '..#..',
                      '.#...',
                      '#####'],
              '3' => ['#####',
                      '...#.',
                      '..#..',
                      '...#.',
                      '....#',
                      '#...#',
                      '.###.'],
              '4' => ['...#.',
                      '..##.',
                      '.#.#.',
                      '#..#.',
                      '#####',
                      '...#.',
                      '...#.'],
              '5' => ['#####',
                      '#....',
                      '####.',
                      '....#',
                      '....#',
                      '#...#',
                      '.###.'],
              '6' => ['..##.',
                      '.#...',
                      '#....',
                      '####.',
                      '#...#',
                      '#...#',
                      '.###.'],
              '7' => ['#####',
                      '....#',
                      '...#.',
                      '..#..',
                      '.#...',
                      '.#...',
                      '.#...'],
              '8' => ['.###.',
                      '#...#',
                      '#...#',
                      '.###.',
                      '#...#',
                      '#...#',
                      '.###.'],
              '9' => ['.###.',
                      '#...#',
                      '#...#',
                      '.####',
                      '....#',
                      '...#.',
                      '.##..'],
          ];
          return isset($font[$ch]) ? $font[$ch] : null;
      }
      /**
       * Tek bir rakami piksel bloklariyla cizer.
       *
       * $p       : bir glif pikselinin ekrandaki blok boyutu
       * $acik    : dolu piksel rengi
       * $govde   : blogun ust kenarinda kullanilan koyu ton (hacim hissi)
       */
      function rakamCiz($im, $rakam, $x, $y, $p, $acik, $govde)
      {
          $g = glif($rakam);
          if ($g === null) { return; }
          foreach ($g as $sy => $satir) {
              for ($sx = 0; $sx < 5; $sx++) {
                  if ($satir[$sx] !== '#') { continue; }
                  $x1 = $x + $sx * $p;
                  $y1 = $y + $sy * $p;
                  $x2 = $x1 + $p - 1;
                  $y2 = $y1 + $p - 1;
                  imagefilledrectangle($im, $x1, $y1, $x2, $y2, $acik);
                  // Her blogun alt-sag kenarinda bir tik koyu ton: pikseller
                  // birbirine yapismaz, izgara okunur kalir.
                  imageline($im, $x1, $y2, $x2, $y2, $govde);
                  imageline($im, $x2, $y1, $x2, $y2, $govde);
              }
          }
      }
      /** Iki nokta ayirac - glif izgarasiyla ayni blok boyutunda. */
      function ayiracCiz($im, $x, $y, $p, $renk, $govde)
      {
          foreach ([1, 4] as $sy) {
              $y1 = $y + $sy * $p + (int) round($p / 2);
              imagefilledrectangle($im, $x, $y1, $x + $p - 1, $y1 + $p - 1, $renk);
              imageline($im, $x, $y1 + $p - 1, $x + $p - 1, $y1 + $p - 1, $govde);
          }
      }
      // ==================================================================
      // CIZIM YARDIMCILARI
      // ==================================================================
      /** Yatayda ortalanmis metin. Harf araligi (tracking) destekli. */
      function yaz($im, $metin, $boyut, $merkezX, $baselineY, $renk, $font, $ttf, $aralik = 0)
      {
          if ($ttf && $aralik > 0) {
              // Harf harf cizip aralik veriyoruz - arcade etiketleri genis olur.
              $harfler = preg_split('//u', $metin, -1, PREG_SPLIT_NO_EMPTY);
              $toplam  = 0;
              $genler  = [];
              foreach ($harfler as $h) {
                  $bb = imagettfbbox($boyut, 0, $font, $h);
                  $g  = $bb[2] - $bb[0];
                  $genler[] = $g;
                  $toplam  += $g + $aralik;
              }
              $toplam -= $aralik;
              $x = (int) ($merkezX - $toplam / 2);
              foreach ($harfler as $i => $h) {
                  imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $h);
                  $x += $genler[$i] + $aralik;
              }
              return;
          }
          if ($ttf) {
              $bbox = imagettfbbox($boyut, 0, $font, $metin);
              $x = (int) ($merkezX - ($bbox[2] - $bbox[0]) / 2);
              imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $metin);
          } else {
              $f = $boyut >= 18 ? 5 : ($boyut >= 11 ? 3 : 2);
              // Dahili fontta aralik icin harf harf cizim
              if ($aralik > 0) {
                  $bg     = imagefontwidth($f);
                  $toplam = strlen($metin) * ($bg + $aralik) - $aralik;
                  $x = (int) ($merkezX - $toplam / 2);
                  $y = (int) ($baselineY - imagefontheight($f));
                  for ($i = 0; $i < strlen($metin); $i++) {
                      imagestring($im, $f, $x, $y, $metin[$i], $renk);
                      $x += $bg + $aralik;
                  }
                  return;
              }
              $x = (int) ($merkezX - imagefontwidth($f) * strlen($metin) / 2);
              $y = (int) ($baselineY - imagefontheight($f));
              imagestring($im, $f, $x, $y, $metin, $renk);
          }
      }
      /** Belirli bir zaman icin hane metinlerini dondurur. */
      function degerler($kalan, $adet, $gunBasamak)
      {
          if ($kalan < 0) { $kalan = 0; }
          $v = [
              str_pad((string) (int) floor($kalan / 86400), $gunBasamak, '0', STR_PAD_LEFT),
              str_pad((string) (int) floor(($kalan % 86400) / 3600), 2, '0', STR_PAD_LEFT),
              str_pad((string) (int) floor(($kalan % 3600) / 60), 2, '0', STR_PAD_LEFT),
              str_pad((string) (int) ($kalan % 60), 2, '0', STR_PAD_LEFT),
          ];
          return array_slice($v, 0, $adet);
      }
      // ------------------------------------------------------------------
      // RALPH
      //
      // Karakteri hazir bir resimden almiyoruz, blok blok ciziyoruz.
      // Kocaman yumruklar, mavi tulum, dagink portakal sac.
      //
      // Asagidaki $b() yardimcisi isi kolaylastiriyor: koordinatlari
      // piksel yerine kucuk kareler halinde veriyoruz, o da $p ile
      // carpip ekrana ciziyor. Yani $b(3, 2, 5, 3) demek "soldan 3.,
      // yukaridan 2. kareden basla, 5 kare genis 3 kare yuksek bir
      // dikdortgen doldur" demek.
      // ------------------------------------------------------------------
      /**
       * Ralph'i cizer.
       *
       * $vurus yumrugun nerede oldugunu belirler:
       *   0 = kol yukarida (hazirlaniyor)
       *   1 = yumruk asagida (tuglaya indi)
       * Arasindaki degerler ara konumlari verir.
       */
      function ralphCiz($im, $x, $y, $p, $vurus, $renkler)
      {
          list($ten, $tulum, $sac, $koyu, $ayakkabi) = $renkler;
          $b = function ($gx, $gy, $gw, $gh, $renk) use ($im, $x, $y, $p) {
              imagefilledrectangle(
                  $im,
                  $x + $gx * $p,
                  $y + $gy * $p,
                  $x + ($gx + $gw) * $p - 1,
                  $y + ($gy + $gh) * $p - 1,
                  $renk
              );
          };
          // Vurusta govde one dogru cokuyor
          $eg = (int) round($vurus);
          // --- Sac (portakal, dagink) ---
          $b(3, 0 + $eg, 5, 1, $sac);
          $b(2, 1 + $eg, 7, 1, $sac);
          $b(2, 0 + $eg, 1, 1, $sac);       // dagink tutamlar
          $b(8, 0 + $eg, 1, 1, $sac);
          // --- Yuz ---
          $b(3, 2 + $eg, 5, 3, $ten);
          $b(3, 3 + $eg, 1, 1, $koyu);      // sol goz
          $b(6, 3 + $eg, 1, 1, $koyu);      // sag goz
          $b(4, 4 + $eg, 3, 1, $koyu);      // agiz - kararli ifade
          // --- Govde (tulum) ---
          $b(3, 5 + $eg, 5, 4, $tulum);
          $b(4, 5 + $eg, 1, 1, $koyu);      // tulum askilari
          $b(6, 5 + $eg, 1, 1, $koyu);
          // --- Sol kol + dev yumruk (sabit, yanda asili) ---
          $b(2, 6 + $eg, 1, 2, $ten);
          $b(0, 8 + $eg, 3, 3, $ten);       // Ralph'in meshur kocaman eli
          $b(0, 8 + $eg, 3, 1, $koyu);
          // --- Sag kol + dev yumruk: vurusa gore yukaridan tuglalara iner ---
          // $vurus 0 -> yumruk basin uzerinde, 1 -> yumruk zeminde
          $yumrukY = (int) round(-2 + $vurus * 11);
          // Omuzdan yumruga uzanan kol
          $omuzY = 6 + $eg;
          $ust   = min($omuzY, $yumrukY + $eg);
          $alt   = max($omuzY, $yumrukY + $eg + 2);
          $b(8, $ust, 1, max(1, $alt - $ust), $ten);
          // Yumruk (3x3 blok - govdeden buyuk, karakterin imzasi)
          $b(8, $yumrukY + $eg, 3, 3, $ten);
          $b(8, $yumrukY + $eg, 3, 1, $koyu);       // parmak eklemleri golgesi
          // --- Bacaklar ---
          $b(3, 9 + $eg, 2, 2, $tulum);
          $b(6, 9 + $eg, 2, 2, $tulum);
          $b(2, 11 + $eg, 3, 1, $ayakkabi);
          $b(6, 11 + $eg, 3, 1, $ayakkabi);
      }
      /**
       * Tugla sirasi. $kirikIndex >= 0 ise o tugla parcalanmis cizilir.
       */
      function tuglaCiz($im, $sol, $sag, $ust, $yuk, $kirikIndex, $renkler)
      {
          list($tugla, $ken, $toz) = $renkler;
          $tg = 26;                              // tugla genisligi
          $adet = (int) ceil(($sag - $sol) / $tg);
          for ($i = 0; $i < $adet; $i++) {
              $x1 = $sol + $i * $tg;
              $x2 = min($x1 + $tg - 2, $sag);
              if ($x1 >= $sag) { break; }
              if ($i === $kirikIndex) {
                  // Parcalanmis tugla: ust yarisi ucmus, altta molozlar
                  imagefilledrectangle($im, $x1, $ust + (int) ($yuk * 0.55), $x2, $ust + $yuk, $ken);
                  // Ucusan parcalar
                  imagefilledrectangle($im, $x1 + 3,  $ust - 5, $x1 + 7,  $ust - 1, $toz);
                  imagefilledrectangle($im, $x1 + 13, $ust - 8, $x1 + 16, $ust - 5, $toz);
                  imagefilledrectangle($im, $x2 - 5,  $ust - 3, $x2 - 2,  $ust,     $toz);
                  continue;
              }
              imagefilledrectangle($im, $x1, $ust, $x2, $ust + $yuk, $tugla);
              imagerectangle($im, $x1, $ust, $x2, $ust + $yuk, $ken);
              // Ust kenar isigi
              imageline($im, $x1 + 1, $ust + 1, $x2 - 1, $ust + 1, $ken);
          }
      }
      // ==================================================================
      // KARE CIZIMI
      // ==================================================================
      /**
       * Tek bir kareyi cizer.
       *
       * @param int $kalan   Kalan saniye
       * @param int $faz     Animasyon fazi (0..N) - yumruk ve titresim icin
       */
      function kareCiz($kalan, $faz)
      {
          global $W, $H, $ADET, $PADDING, $PIX, $HANE_G, $HANE_BOS, $GRUP_BOS, $BASAMAKLAR,
                 $EKRAN_SOL, $EKRAN_UST, $EKRAN_G, $EKRAN_YUK, $TUGLA_YUK,
                 $RAKAM_UST, $RAKAM_YUK, $ETIKET_Y, $grupGen,
                 $RALPH_P, $RALPH_G, $RALPH_BOS,
                 $BASLIK, $BITTI_METNI, $ETIKETLER, $FONT, $ttf, $T, $tema, $gunBasamak;
          $im = imagecreatetruecolor($W, $H);
          imagealphablending($im, false);
          imagesavealpha($im, true);
          $r = function ($c, $a = 0) use ($im) {
              return imagecolorallocatealpha($im, $c[0], $c[1], $c[2], $a);
          };
          // ---------- ZEMIN ----------
          // Seffaf temada zemini isaret rengiyle boyuyoruz; o renk daha
          // sonra GIF'e yazilirken saydam olarak isaretleniyor.
          // (Ayrintili aciklama icin yukaridaki SEFFAF_R tanimina bakin.)
          if ($tema === 'seffaf') {
              imagefilledrectangle(
                  $im, 0, 0, $W - 1, $H - 1,
                  imagecolorallocate($im, SEFFAF_R, SEFFAF_G, SEFFAF_B)
              );
          } else {
              imagefilledrectangle($im, 0, 0, $W - 1, $H - 1, $r($T['zemin']));
          }
          imagealphablending($im, true);
          // ---------- DOLAP CERCEVESI ----------
          // Ekranin cevresinde ince cift cizgi - arcade dolabinin bezel'i.
          // Seffaf temada yari saydam tonlar nobetci rengi karistiracagi icin
          // tum cerceve ogeleri opak cizilir.
          $sf = ($tema === 'seffaf');
          $cerceve = $r($T['cerceve']);
          imagerectangle($im, 1, 1, $W - 2, $H - 2, $cerceve);
          imagerectangle($im, 3, 3, $W - 4, $H - 4, $sf ? $cerceve : $r($T['cerceve'], 60));
          // Kose vidalari (dolap detayi)
          $vida = $sf ? $r($T['baslik']) : $r($T['baslik'], 40);
          foreach ([[7, 7], [$W - 8, 7], [7, $H - 8], [$W - 8, $H - 8]] as $v) {
              imagefilledellipse($im, $v[0], $v[1], 3, 3, $vida);
          }
          // ---------- BASLIK SERIDI ----------
          // Marquee: dolabin ustundeki isikli tabela.
          // Seffaf temada opak cizilir - yoksa altindaki nobetci renk sizar.
          $mSol = $PADDING;
          $mSag = $W - $PADDING;
          if ($tema === 'acik') {
              // Acik temada tabela zeminle ayni tonda kalir, yazi koyu olur.
              imagefilledrectangle($im, $mSol, 10, $mSag, 30, $r($T['golge']));
          } else {
              imagefilledrectangle(
                  $im, $mSol, 10, $mSag, 30,
                  ($tema === 'seffaf') ? $r($T['ekran']) : $r($T['ekran'], 40)
              );
          }
          imagerectangle($im, $mSol, 10, $mSag, 30, $r($T['cerceve'], 50));
          yaz($im, $BASLIK, 8, $W / 2, 25, $r($T['marquee']), $FONT, $ttf, 2);
          // ---------- CRT EKRANI ----------
          $eSol = $EKRAN_SOL;
          $eSag = $W - $PADDING;
          $eUst = $EKRAN_UST;
          $eAlt = $H - $PADDING;
          imagefilledrectangle($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran']));
          // Ekran ici parlama: ustten asagi sonen ince cam yansimasi
          for ($i = 0; $i < 10; $i++) {
              imagefilledrectangle(
                  $im,
                  $eSol + 1, $eUst + 1 + $i,
                  $eSag - 1, $eUst + 1 + $i,
                  $r($T['siyan'], 116 + $i)
              );
          }
          // ---------- SURE DOLDU ----------
          // Ralph isini bitirmis: tuglalar yikilmis, molozun uzerinde duruyor.
          if ($kalan <= 0) {
              $tUstB = $eAlt - $TUGLA_YUK + 4;
              // Yikilmis tugla sirasi - hepsi kirik, yalnizca moloz kalmis
              $tg = 26;
              for ($bx = $eSol + 2; $bx < $eSag - 2; $bx += $tg) {
                  $bx2 = min($bx + $tg - 2, $eSag - 2);
                  $yh  = (int) (($TUGLA_YUK - 6) * 0.45);
                  imagefilledrectangle(
                      $im, $bx, $tUstB + ($TUGLA_YUK - 6) - $yh, $bx2, $tUstB + $TUGLA_YUK - 6,
                      $r($T['tuglaKen'])
                  );
              }
              yaz($im, $BITTI_METNI, 18, $W / 2, $eUst + 52, $r($T['magenta']), $FONT, $ttf, 4);
              // Ralph, molozun uzerinde - yumruk asagida, is bitmis
              ralphCiz(
                  $im, $eSag - $RALPH_G - 6, $tUstB - 12 * $RALPH_P + 2, $RALPH_P, 1.0,
                  [
                      $r($T['ten']), $r($T['tulum']), $r($T['amber']),
                      $r($T['mor']), $r($T['tuglaKen']),
                  ]
              );
              tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
              return $im;
          }
          $vals = degerler($kalan, $ADET, $gunBasamak);
          // ---------- VURUS RITMI ----------
          // Ralph her saniye bir kez vuruyor: kareler sirayla "yumruk
          // asagida" ve "kol yukarida" diye gidiyor. Bu yuzden tek sayili
          // ve cift sayili kareler farkli cizilir.
          //
          // Rakamlarin kendisi sabit duruyor. Vurusta onlari da
          // titretmeyi denedim ama o zaman her karede butun rakam satiri
          // degismis sayiliyor ve dosya boyutu ucuyor. Hareket sadece
          // Ralph'ta ve tuglalarda kaliyor.
          $kirik = -1;
          $adim = $faz % 2;   // 0 = yumruk zeminde, 1 = kol yukarida
          if ($adim === 0) {
              $vurusFaz = 1.0;
              // Kirilan tuglayi her saniye degistiriyoruz ki Ralph hep
              // ayni yere vurmasin.
              $kirik = ((int) $kalan) % 9;
          } else {
              $vurusFaz = 0.15;
          }
          // ---------- RENKLER ----------
          $acik  = $r($T['siyan']);
          $govde = $r($T['mor'], 40);   // blok kenarlarindaki koyu ton
          $vurgu = $r($T['amber']);
          // Son 10 saniyede rakamlar pembeye doner - aciliyet hissi.
          if ($kalan <= 10) {
              $acik = $r($T['magenta']);
          }
          // ---------- RAKAMLARI YERLESTIR ----------
          // Rakamlar soldan basliyor; Ralph icin sagda yer ayrilmisti,
          // bu yuzden ikisi hicbir zaman cakismaz.
          $x  = $eSol + 10;
          $ry = $RAKAM_UST;
          foreach ($vals as $g => $deger) {
              $n  = $BASAMAKLAR[$g];
              $gy = $ry;
              for ($d = 0; $d < $n; $d++) {
                  $hx = $x + $d * ($HANE_G + $HANE_BOS);
                  rakamCiz($im, $deger[$d], $hx, $gy, $PIX, $acik, $govde);
              }
              // Etiket
              $grupMerkez = $x + $grupGen[$g] / 2;
              yaz($im, $ETIKETLER[$g], 7, $grupMerkez, $ETIKET_Y, $r($T['baslik']), $FONT, $ttf, 2);
              $x += $grupGen[$g];
              // Ayirac
              if ($g < $ADET - 1) {
                  // Yanip sonme: vurus aninda parlak
                  $aRenk = ($adim === 0) ? $vurgu : $r($T['amber'], 90);
                  ayiracCiz($im, $x + (int) round(($GRUP_BOS - $PIX) / 2), $gy, $PIX, $aRenk, $govde);
                  $x += $GRUP_BOS;
              }
          }
          // ---------- TUGLA SIRASI + RALPH ----------
          $tUst = $eAlt - $TUGLA_YUK + 4;
          tuglaCiz(
              $im, $eSol + 2, $eSag - 2, $tUst, $TUGLA_YUK - 6, $kirik,
              [$r($T['tugla']), $r($T['tuglaKen']), $r($T['amber'], 40)]
          );
          // Ralph rakamlarin sagindaki kendi seridinde, tuglalarin uzerinde durur
          $ralphP = $RALPH_P;
          $ralphX = $eSag - $RALPH_G - 6;
          $ralphY = $tUst - 12 * $ralphP + 2;
          ralphCiz(
              $im, $ralphX, $ralphY, $ralphP, $vurusFaz,
              [
                  $r($T['ten']),
                  $r($T['tulum']),
                  [$r($T['amber'])][0],
                  $r($T['mor']),
                  $r($T['tuglaKen']),
              ]
          );
          // ---------- CRT TARAMA CIZGILERI ----------
          tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
          return $im;
      }
      /** CRT tarama cizgileri - her 3 pikselde bir yatay koyu cizgi. */
      function tarama($im, $sol, $ust, $sag, $alt, $renk)
      {
          for ($y = $ust; $y <= $alt; $y += 3) {
              imageline($im, $sol + 1, $y, $sag - 1, $y, $renk);
          }
      }
      // ==================================================================
      // GIF BIRLESTIRICI  —  isin can alici noktasi
      //
      // PHP'nin GD eklentisi animasyonlu GIF uretemez; imagegif() her
      // seferinde tek kareli bir GIF verir. Bu yuzden animasyonu elle
      // kuruyoruz:
      //
      //   1) Her kareyi ayri ayri cizip GD'ye tek kareli GIF yaptiriyoruz.
      //   2) O GIF'lerin icini acip sikistirilmis goruntu verisini ve
      //      renk paletini cekiyoruz (asagidaki gifCoz fonksiyonu).
      //   3) GIF89a dosya yapisini kendimiz yazip kareleri arka arkaya
      //      diziyoruz (en alttaki "GIF'I KUR" bolumu).
      //
      // Goruntu verisini yeniden sikistirmiyoruz; GD'nin sikistirdigi
      // hazir bloklari oldugu gibi tasiyoruz. Bu yuzden hizli calisir.
      // ==================================================================
      /**
       * Tek kareli bir GIF'in icinden renk paletini ve sikistirilmis
       * goruntu verisini cikarir. GIF dosyasi bloklardan olusur; basindan
       * baslayip blok blok ilerliyor, isimize yarayan yere gelince duruyoruz.
       */
      function gifCoz($bin)
      {
          $packed = ord($bin[10]);
          $p      = 13;
          $gct    = '';
          $gctBit = 0;
          if ($packed & 0x80) {
              $gctBit = $packed & 0x07;
              $len    = 3 * (1 << ($gctBit + 1));
              $gct    = substr($bin, $p, $len);
              $p     += $len;
          }
          $son  = strlen($bin);
          $seffafIdx = -1;
          while ($p < $son) {
              $b = ord($bin[$p]);
              if ($b === 0x21) {                       // uzanti blogu
                  // Graphic Control Extension ise seffaf renk indeksini al
                  if ($p + 1 < $son && ord($bin[$p + 1]) === 0xF9) {
                      $bayrak = ord($bin[$p + 3]);
                      if ($bayrak & 0x01) { $seffafIdx = ord($bin[$p + 6]); }
                  }
                  $p += 2;
                  while ($p < $son && ($sz = ord($bin[$p])) !== 0) { $p += 1 + $sz; }
                  $p++;
              } elseif ($b === 0x2C) {                 // goruntu tanimlayici
                  $ipacked = ord($bin[$p + 9]);
                  $p += 10;
                  $pal = $gct;
                  $bit = $gctBit;
                  if ($ipacked & 0x80) {
                      $bit  = $ipacked & 0x07;
                      $len  = 3 * (1 << ($bit + 1));
                      $pal  = substr($bin, $p, $len);
                      $p   += $len;
                  }
                  $lzwMin = $bin[$p];
                  $p++;
                  $veri = '';
                  while ($p < $son && ($sz = ord($bin[$p])) !== 0) {
                      $veri .= substr($bin, $p, 1 + $sz);
                      $p    += 1 + $sz;
                  }
                  $veri .= "\x00";
                  return [
                      'pal'    => $pal,
                      'bit'    => $bit,
                      'min'    => $lzwMin,
                      'veri'   => $veri,
                      'seffaf' => $seffafIdx,
                  ];
              } else {
                  break;
              }
          }
          return null;
      }
      /**
       * Cizilmis bir resmi GIF verisine cevirir.
       *
       * $renkSayisi renk sayisini kisitlar (32, 64 gibi). Az renk =
       * kucuk dosya. Tasarimda zaten az renk kullandigimiz icin gozle
       * gorulur bir kayip olmuyor.
       *
       * Sira onemli: once renkler kisitlaniyor, SONRA saydam renk
       * isaretleniyor. Tersi yapilirsa saydamlik kayboluyor.
       */
      function gifBin($im, $renkSayisi = 0)
      {
          global $tema;
          if ($renkSayisi > 0) {
              imagetruecolortopalette($im, false, $renkSayisi);
          }
          if ($tema === 'seffaf') {
              // Isaret rengini palette buluyoruz. Renk kisitlama sirasinda
              // ton biraz kaymis olabilecegi icin en yakin esi ariyoruz.
              $idx = imagecolorclosest($im, SEFFAF_R, SEFFAF_G, SEFFAF_B);
              if ($idx >= 0) {
                  imagecolortransparent($im, $idx);
              }
          }
          imagesavealpha($im, true);
          ob_start();
          imagegif($im);
          return ob_get_clean();
      }
      // ==================================================================
      // KARELERI URET
      //
      // Dosya boyutunu kucuk tutan numara burada. GIF'te her kareyi tam
      // boy gondermek zorunda degilsiniz; sadece degisen bolgeyi gonderip
      // "geri kalani oldugu gibi birak" diyebilirsiniz.
      //
      // Sayacta bir saniyede degisen sey cok az: bir iki rakam, yanip
      // sonen iki nokta ve Ralph. Ekranin geri kalani ayni kaliyor. Bu
      // yuzden her karede yalnizca degisen kucuk dikdortgenleri yaziyoruz.
      // Fark: 210 KB yerine 75 KB.
      // ==================================================================
      /**
       * Iki kareyi karsilastirip degisen bolgeleri dikdortgen listesi
       * olarak dondurur.
       *
       * Neden tek dikdortgen degil de liste? Cunku rakamlar solda, Ralph
       * sagda duruyor. Ikisini tek bir dikdortgene sigdirsaydik aradaki
       * kocaman degismemis alan da her karede bosuna gonderilirdi.
       * Onun yerine degisen sutunlari gruplayip her gruba kendi
       * dikdortgenini veriyoruz - GIF ayni karede birden fazla parcayi
       * zaten destekliyor.
       *
       * @return array  [[sol, ust, genislik, yukseklik], ...]
       *                Hicbir sey degismemisse bos dizi doner.
       */
      function farkKutulari($a, $b, $w, $h)
      {
          // Once degisen sutunlari ve satirlari isaretle
          $sutun = array_fill(0, $w, false);
          $ust = $h; $alt = -1;
          for ($y = 0; $y < $h; $y++) {
              $satirDegisti = false;
              for ($x = 0; $x < $w; $x++) {
                  if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
                      $sutun[$x]    = true;
                      $satirDegisti = true;
                  }
              }
              if ($satirDegisti) {
                  if ($y < $ust) { $ust = $y; }
                  $alt = $y;
              }
          }
          if ($alt < 0) { return []; }
          // Bitisik degisen sutunlari gruplara ayir.
          // Aralarinda $ESIK'ten az bos sutun varsa ayni gruba katilir - cok sayida
          // minik blok, palet yuku yuzunden tek bloktan pahali olurdu.
          $ESIK   = 24;
          $gruplar = [];
          $bas    = -1;
          $bosluk = 0;
          for ($x = 0; $x < $w; $x++) {
              if ($sutun[$x]) {
                  if ($bas < 0) { $bas = $x; }
                  $son    = $x;
                  $bosluk = 0;
              } elseif ($bas >= 0) {
                  $bosluk++;
                  if ($bosluk > $ESIK) {
                      $gruplar[] = [$bas, $son];
                      $bas = -1;
                  }
              }
          }
          if ($bas >= 0) { $gruplar[] = [$bas, $son]; }
          // Her grup icin dikey sinirlari daralt
          $kutular = [];
          foreach ($gruplar as $g) {
              list($gs, $gson) = $g;
              $gUst = $h; $gAlt = -1;
              for ($y = $ust; $y <= $alt; $y++) {
                  for ($x = $gs; $x <= $gson; $x++) {
                      if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
                          if ($y < $gUst) { $gUst = $y; }
                          $gAlt = $y;
                          break;
                      }
                  }
              }
              if ($gAlt < 0) { continue; }
              $kutular[] = [$gs, $gUst, $gson - $gs + 1, $gAlt - $gUst + 1];
          }
          return $kutular;
      }
      // Uretilen parcalar burada birikiyor. Her eleman:
      //   [sol, ust, goruntuVerisi, kareninSonuMu]
      $kareler     = [];
      $oncekiIm    = null;      // karsilastirma icin bir onceki kare
      $oncekiBitti = null;
      // Saniye saniye ilerleyip her an icin bir kare ciziyoruz.
      for ($i = 0; $i < $KARE_SAYISI; $i++) {
          $kalan = $hedefTs - ($simdi + $i);
          $bitti = ($kalan <= 0);
          // Sure dolduktan sonra ekran hic degismiyor; tek bir
          // "OYUN BITTI" karesi yeter, gerisini uretmiyoruz.
          if ($bitti && $oncekiBitti) { continue; }
          $im = kareCiz($kalan, $i);
          if ($oncekiIm === null) {
              // Ilk kare tam boy gonderilir - uzerine eklenecek bir sey
              // olmadigi icin zemin gorevi gorur.
              //
              // Kucuk bir tuzak: gifBin() resmin renklerini yerinde
              // degistirdigi icin, karsilastirmada kullanacagimiz temiz
              // kopyayi onceden almamiz gerekiyor. Yoksa bir sonraki
              // kareyle karsilastirdigimizda her piksel farkli cikardi.
              $kopya = imagecreatetruecolor($W, $H);
              imagealphablending($kopya, false);
              imagesavealpha($kopya, true);
              imagecopy($kopya, $im, 0, 0, 0, 0, $W, $H);
              $kareler[] = [0, 0, gifBin($im, 128), true];
              imagedestroy($im);
              $im = $kopya;
          } else {
              $kutular = farkKutulari($oncekiIm, $im, $W, $H);
              if (!$kutular) {
                  // Hicbir sey degismedi - kareyi atla
                  imagedestroy($im);
                  continue;
              }
              $sonIdx = count($kutular) - 1;
              foreach ($kutular as $ki => $kutu) {
                  list($sol, $ust, $gen, $yuk) = $kutu;
                  $kirp = imagecreatetruecolor($gen, $yuk);
                  imagealphablending($kirp, false);
                  imagesavealpha($kirp, true);
                  imagecopy($kirp, $im, 0, 0, $sol, $ust, $gen, $yuk);
                  // Yalnizca son blok gecikme tasir; oncekiler ayni anda cizilir.
                  $kareler[] = [$sol, $ust, gifBin($kirp, 32), $ki === $sonIdx];
                  imagedestroy($kirp);
              }
          }
          if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
          $oncekiIm    = $im;
          $oncekiBitti = $bitti;
      }
      if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
      // ==================================================================
      // GIF'I KUR
      //
      // Elimizde kare kare goruntu parcalari var; simdi bunlari GIF89a
      // dosya yapisina gore arka arkaya diziyoruz. Sira sabittir:
      //   basik bilgi -> renk paleti -> dongu ayari -> kareler -> bitis
      // ==================================================================
      $ilk = gifCoz($kareler[0][2]);
      // --- Dosya basligi: imza, olculer, renk paleti ---
      $out  = "GIF89a";
      $out .= pack('v', $W) . pack('v', $H);
      $out .= chr(0x80 | 0x70 | $ilk['bit']) . "\x00\x00";
      $out .= $ilk['pal'];
      // --- Dongu ayari ---
      // Tarayicilara animasyonun kac kez oynayacagini soyleyen blok.
      // ($DONGU 1 ise bir kez oynar, 0 ise sonsuz doner.)
      $out .= "\x21\xFF\x0B" . "NETSCAPE2.0" . "\x03\x01" . pack('v', $DONGU) . "\x00";
      // --- Kareler ---
      foreach ($kareler as $idx => $k) {
          list($sol, $ust, $bin, $gecikmeVer) = $k;
          $f = gifCoz($bin);
          if ($f === null) { continue; }
          // Her parcanin onune bir kontrol blogu koyuyoruz. Burada iki
          // sey soyluyoruz: bu parca ne kadar ekranda kalacak ve
          // silinmeden yerinde birakilacak (silinseydi altindaki kare
          // kaybolur, ekran yanip sonerdi).
          //
          // Bir kareyi birden fazla parcaya bolmustuk. O parcalarin
          // yalnizca sonuncusuna bekleme suresi veriyoruz; oncekiler
          // 0 sureyle ard arda cizildigi icin izleyici tek kare gorur.
          $bayrak = (1 << 2);
          $seffafIdx = 0;
          if ($f['seffaf'] >= 0) {
              $bayrak |= 0x01;
              $seffafIdx = $f['seffaf'];
          }
          $out .= "\x21\xF9\x04" . chr($bayrak)
                . pack('v', $gecikmeVer ? $KARE_SURESI : 0)
                . chr($seffafIdx) . "\x00";
          // Parcanin ekranda nereye oturacagini ve olculerini yaziyoruz.
          // Tam boy olmadigi icin konum bilgisi onemli: tarayici bu
          // parcayi onceki karenin uzerine, tam bu noktaya cizecek.
          $gen = ord($bin[6]) | (ord($bin[7]) << 8);
          $yuk = ord($bin[8]) | (ord($bin[9]) << 8);
          $out .= "\x2C" . pack('v', $sol) . pack('v', $ust) . pack('v', $gen) . pack('v', $yuk);
          // Parcanin kendi renk paleti ve sikistirilmis goruntu verisi.
          $out .= chr(0x80 | $f['bit']) . $f['pal'];
          $out .= $f['min'] . $f['veri'];
      }
      // --- Dosya sonu isareti ---
      $out .= "\x3B";
      // ==================================================================
      // ONBELLEGE YAZ
      // ==================================================================
      if ($onbellekDosya !== null) {
          // Once gecici bir dosyaya yazip sonra asil adina tasiyoruz.
          // Boylece ayni anda gelen baska bir istek, yarim yazilmis
          // dosyayi okuyup bozuk resim gostermez.
          $gecici = $onbellekDosya . '.' . getmypid() . '.tmp';
          if (@file_put_contents($gecici, $out) === strlen($out)) {
              if (!@rename($gecici, $onbellekDosya)) { @unlink($gecici); }
          } else {
              // Yazamadiysak (izin yok, disk dolu) sorun degil; resim
              // yine gonderilir, sadece onbellekten yararlanilmaz.
              @unlink($gecici);
          }
          // Ayarlari her degistirdiginizde yeni bir dosya adi olusur ve
          // eskisi oldugu yerde kalir. Klasor sismesin diye ara sira
          // (yaklasik her 50 istekte bir) 1 saatten eski dosyalari
          // siliyoruz. Her istekte yapmak gereksiz yere disk yorardi.
          if (mt_rand(1, 50) === 1) {
              foreach ((array) @glob($ONBELLEK_DIZ . '/*.gif') as $eski) {
                  if (time() - (int) @filemtime($eski) > 3600) { @unlink($eski); }
              }
          }
      }
      // ==================================================================
      // CIKTI
      // ==================================================================
      gifGonder($out);
  • 08-08-2026, 06:20:41
    #2
    link nerde hocam

    Edit: Konu içindeymiş Güzel olmuş
  • 08-08-2026, 06:22:00
    #3
    Ralph ve İnternet animasyonundaki Ralph her yere vurduğunda saniye ile oynuyor 😄
    Sizin sistem anlık saymıyor, PNG olduğu için sayfa yenilenene kadar donuk kalıyor. PNG yerine GIF kullanırsanız anlık saydırabilirsiniz — GIF animasyon destekliyor, tarayıcı kendi oynattığı için sayfa yenilenmeden sayaç ilerliyor.
    Mantık şöyle: sunucu istek geldiği anda 60 saniyelik bir animasyon üretiyor, her kare bir saniyeyi gösteriyor. Tarayıcı da onu oynatıyor. Ekstra bir kütüphaneye gerek yok, PHP'nin GD'si yetiyor.
    Eline sağlık 🙂

  • 08-08-2026, 06:26:42
    #4
    gif sadece 1 dakia oynar sonra donacaktır bekledim donduğunu gördüm o yüzden seçmedim
  • 08-08-2026, 06:28:39
    #5
    UbrTrade adlı üyeden alıntı: mesajı görüntüle
    gif sadece 1 dakia oynar sonra donacaktır bekledim donduğunu gördüm o yüzden seçmedim
    Doğru hocam, ben sadece mantığı anlatmak istedim, GD ile güzel işler yapılabilir onun için. İyi forumlar
  • 08-08-2026, 06:29:38
    #6
    bnsware adlı üyeden alıntı: mesajı görüntüle
    Doğru hocam, ben sadece mantığı anlatmak istedim, GD ile güzel işler yapılabilir onun için. İyi forumlar
    ama beğendim kullanmak isterim paylaşırsanız sonuçta sizinki daha güzel olmuş
  • 08-08-2026, 06:40:23
    #7
    Bu cevap, konu sahibi tarafından kabul edilebilir bir cevap olarak işaretlendi.
    UbrTrade adlı üyeden alıntı: mesajı görüntüle
    ama beğendim kullanmak isterim paylaşırsanız sonuçta sizinki daha güzel olmuş
    Elbette hocam, elimden geldiğince yorum satırlarıyla ne nedir, ne işe yarar anlatmaya çalıştım, spoilerden kodu görebilirsiniz

    <?php
    /**
     * ARCADE GERI SAYIM SAYACI  —  animasyonlu GIF
     * ---------------------------------------------------------------
     *
     * NEDEN GIF?
     * PNG durgun bir resimdir; sayfa yenilenmeden degismez. GIF ise
     * animasyon tasiyabildigi icin, sunucu istek geldigi anda 60
     * saniyelik bir animasyon uretir ve tarayici bunu kendi oynatir.
     * Sonuc: sayac sayfa yenilenmeden tiklar. Forumlar <img> etiketine
     * izin verdigi icin de konu icinde, imzada, her yerde calisir.
     *
     * KULLANIM
     *   HTML   : <img src="https://siteniz.com/index.php">
     *   BBCode : [img]https://siteniz.com/index.php[/img]
     *
     * TEMALAR (adresin sonuna eklenir)
     *   ?tema=koyu     koyu zemin (varsayilan)
     *   ?tema=acik     acik zemin
     *   ?tema=seffaf   zemin saydam; forum kendi arka planini gosterir,
     *                  yani hem koyu hem acik temali forumda uyar
     *
     * KURULUM
     *   1) Bu dosyayi sunucuya atin.
     *   2) Yanina "onbellek" adinda bos bir klasor acip yazma izni
     *      verin (755). Acmazsaniz yine calisir, sadece her istekte
     *      bastan uretir - yani daha yavas olur.
     *   3) Turkce karakter (GUN yerine GÜN) isterseniz ayni klasore
     *      "font.ttf" atin. Rakamlar fonta ihtiyac duymaz, onlari kod
     *      kendi cizer; font yalnizca alt etiketler icindir.
     *
     * GEREKSINIM
     *   PHP + GD eklentisi (imagegif). Baska kutuphane yok.
     *
     * NOT
     *   Animasyon 60 saniyeliktir; sonunda son karede durur. Sayfa
     *   yenilendiginde bastan uretilir. Daha uzun sursun isterseniz
     *   asagidaki $KARE_SAYISI degerini buyutun (dosya boyutu artar).
     */
    // ==================================================================
    // AYARLAR  —  duzenlemeniz gereken tek bolum burasi
    // ==================================================================
    date_default_timezone_set('Europe/Istanbul');
    // Geri sayimin bitecegi an. Bicim: 'YIL-AY-GUN SAAT:DAKIKA:SANIYE'
    $HEDEF       = '2026-09-15 14:13:00';
    // Sayacin ustundeki tabelada yazan metin
    $BASLIK      = 'UGILABS - GIF Versiyon';
    // Sure doldugunda ekranda yazacak metin
    $BITTI_METNI = 'OYUN BITTI';
    // ------------------------------------------------------------------
    // ANIMASYON
    // ------------------------------------------------------------------
    // Kac saniyelik animasyon uretilsin. 60 = 1 dakika.
    // Buyutursen daha uzun sure tiklar ama dosya buyur:
    //   60 kare  ~75 KB  (1 dakika)
    //  300 kare ~250 KB  (5 dakika)
    $KARE_SAYISI  = 60;
    // Her karenin ekranda kalma suresi (1/100 saniye cinsinden).
    // 100 = 1 saniye. Sayac saniye saydigi icin bunu degistirme.
    $KARE_SURESI  = 100;
    // 1 = bir kez oynar, sonra son karede durur.
    // 0 = sonsuz doner. Surekli hareket eder ama her turda basa
    //     saracagi icin yanlis zaman gostermeye baslar. Sayac icin
    //     1 dogrusu; sonsuz dongu ancak susleme amacli isine yarar.
    $DONGU        = 1;
    // false yaparsan saniye hanesi kaybolur, sadece gun/saat/dakika
    // kalir. O durumda animasyona gerek kalmaz, tek kare uretilir.
    $SANIYE_GOSTER = true;
    // ------------------------------------------------------------------
    // ONBELLEK  —  paylasimli hostingde sunucuyu ayakta tutar
    //
    // Her istekte animasyonu bastan cizmek sunucuyu yorar (~0.8 sn).
    // Populer bir konuda 50 kisi ayni anda sayfayi acarsa bu 40 saniye
    // islemci demektir ve paylasimli hosting bunu kaldirmaz.
    //
    // Bu yuzden uretilen GIF diske yazilir; asagidaki sure boyunca
    // gelen istekler yeniden cizmeden hazir dosyayi alir. 50 kisi ayni
    // anda girse bile tek uretim yapilir.
    //
    // Sayacin dogrulugunu bozmaz: en fazla bu sure kadar geriden gelir,
    // yani 3 saniyede kimsenin fark edecegi bir sapma olusmaz.
    // ------------------------------------------------------------------
    // Uretilen dosya kac saniye saklansin. 0 yazarsan onbellek kapanir.
    $ONBELLEK_SN  = 3;
    // Dosyalarin yazilacagi klasor. Yazma izni yoksa sistem yine
    // calisir, sadece her istekte bastan uretir.
    $ONBELLEK_DIZ = __DIR__ . '/onbellek';
    // ------------------------------------------------------------------
    // FONT
    // Rakamlar fonta ihtiyac duymaz - onlari kod nokta nokta cizer.
    // Buradaki font yalnizca alt etiketler (GUN, SAAT...) ve baslik
    // icindir. Turkce karakter isterseniz klasore font.ttf atin;
    // bulunamazsa asagidaki sistem fontlari sirayla denenir.
    // ------------------------------------------------------------------
    $FONT_ADAYLARI = [
        __DIR__ . '/font.ttf',
        'C:/Windows/Fonts/consolab.ttf',
        '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
        '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
    ];
    // ------------------------------------------------------------------
    // TEMA  —  arcade dolabinin ekrani
    // ------------------------------------------------------------------
    $tema = isset($_GET['tema']) ? strtolower($_GET['tema']) : 'koyu';
    if (!in_array($tema, ['koyu', 'acik', 'seffaf'], true)) { $tema = 'koyu'; }
    $T = [
        // Ortak arcade paleti - zeminden bagimsiz, her iki temada da okunur.
        'siyan'    => [ 34, 224, 232],
        'magenta'  => [255,  61, 139],
        'amber'    => [255, 197,  61],
        'mor'      => [ 27,  16,  53],
        'ten'      => [217, 142,  95],
        'tulum'    => [ 46,  92, 138],
        'tugla'    => [166,  74,  58],
        'tuglaKen' => [ 92,  38,  30],
    ];
    // 'ekran' her temada koyu kalir - CRT camı zeminden bagimsizdir.
    // 'baslik'  : ekranin ICINDEKI etiketler (hep koyu zemin uzerinde)
    // 'marquee' : ekranin USTUNDEKI tabela yazisi (zemine gore degisir)
    if ($tema === 'acik') {
        $T['zemin']   = [232, 230, 240];
        $T['ekran']   = [ 38,  24,  70];
        $T['cerceve'] = [ 62,  44, 104];
        $T['baslik']  = [166, 152, 208];
        $T['marquee'] = [ 44,  30,  84];   // acik zeminde koyu yazi
        $T['golge']   = [206, 202, 220];
    } else {
        $T['zemin']   = [ 16,  10,  30];
        $T['ekran']   = [ 30,  18,  58];
        $T['cerceve'] = [ 62,  44, 104];
        $T['baslik']  = [148, 132, 196];
        $T['marquee'] = [172, 156, 216];
        $T['golge']   = [ 10,   6,  20];
    }
    // ==================================================================
    // ONBELLEKTEN SUNMA
    //
    // Bu bolum dosyanin en basinda duruyor cunku amaci cizim islerine
    // hic girmeden isi bitirmek. Yeterince taze bir dosya varsa onu
    // gonderip cikiyoruz; asagidaki kodun tamami hic calismiyor.
    // ==================================================================
    /** Hazir GIF verisini tarayiciya gonderir ve betigi sonlandirir. */
    function gifGonder($veri)
    {
        header('Content-Type: image/gif');
        header('Content-Length: ' . strlen($veri));
        // Tarayiciya "bu resmi saklama" diyoruz. Saklarsa sayfa
        // yenilendiginde eski animasyonu gosterir ve sayac yanlis
        // zamanda kalir.
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');
        header('Expires: Sat, 01 Jan 2000 00:00:00 GMT');
        echo $veri;
        exit;
    }
    $onbellekDosya = null;
    if ($ONBELLEK_SN > 0) {
        // Dosya adini ayarlardan uretiyoruz: ayni ayarlar ayni dosyayi
        // kullanir, farkli tema kendi dosyasini alir. Boylece temalar
        // birbirine karismaz.
        $anahtar = md5(implode('|', [
            $HEDEF, $BASLIK, $BITTI_METNI, $tema,
            $KARE_SAYISI, $KARE_SURESI, $DONGU, $SANIYE_GOSTER,
        ]));
        if (!is_dir($ONBELLEK_DIZ)) { @mkdir($ONBELLEK_DIZ, 0755, true); }
        $onbellekDosya = $ONBELLEK_DIZ . '/' . $anahtar . '.gif';
        // Dosya var ve yeterince taze mi? Oyleyse hic cizmeden gonder.
        if (is_readable($onbellekDosya)) {
            $yas = time() - (int) @filemtime($onbellekDosya);
            if ($yas >= 0 && $yas < $ONBELLEK_SN) {
                $hazir = @file_get_contents($onbellekDosya);
                if ($hazir !== false && strlen($hazir) > 0) {
                    gifGonder($hazir);
                }
            }
        }
        // Buraya dustuyse dosya yok ya da eskimis; asagida yeniden
        // uretilip en sonda diske yazilacak.
    }
    // ==================================================================
    // HAZIRLIK
    // ==================================================================
    $FONT = null;
    foreach ($FONT_ADAYLARI as $f) {
        if (is_readable($f)) { $FONT = $f; break; }
    }
    $ttf = ($FONT !== null);
    $hedefTs = strtotime($HEDEF);
    $simdi   = time();
    if (!$SANIYE_GOSTER) { $KARE_SAYISI = 1; }
    $ETIKETLER = ['GUN', 'SAAT', 'DAKIKA', 'SANIYE'];
    $ADET      = $SANIYE_GOSTER ? 4 : 3;
    // ------------------------------------------------------------------
    // OLCULER
    // Ekran bir arcade dolabinin CRT'si: disinda cerceve, icinde rakamlar,
    // en altta Ralph'in yumruk attigi tugla siras var.
    // ------------------------------------------------------------------
    $PADDING   = 14;   // dis cerceve kalinligi
    // Rakamlar 5x7 glif izgarasi. $PIX bir glif pikselinin blok boyutu.
    $PIX       = 6;
    $HANE_G    = 5 * $PIX;          // bir rakam hanesinin genisligi
    $RAKAM_YUK = 7 * $PIX;          // glif yuksekligi
    $HANE_BOS  = $PIX;              // ayni grup icindeki haneler arasi
    $GRUP_BOS  = $PIX * 3;          // gruplar arasi (iki nokta buraya gelir)
    // Gun hanesi 3 basamak olabilir (999 gun), digerleri hep 2 basamak.
    $gunBasamak = max(2, strlen((string) (int) floor(max(0, $hedefTs - $simdi) / 86400)));
    $BASAMAKLAR = [$gunBasamak, 2, 2, 2];
    $BASAMAKLAR = array_slice($BASAMAKLAR, 0, $ADET);
    $grupGen = [];
    foreach ($BASAMAKLAR as $n) {
        $grupGen[] = $n * $HANE_G + ($n - 1) * $HANE_BOS;
    }
    // Rakam bloklarinin toplam genisligi
    $RAKAM_G = array_sum($grupGen) + ($ADET - 1) * $GRUP_BOS;
    // Ralph rakamlarin SAGINDA kendi seridinde durur - etiketleri kapatmaz.
    $RALPH_P   = 3;                  // Ralph'in piksel blok boyutu
    $RALPH_G   = 13 * $RALPH_P;      // Ralph + yumruk icin ayrilan genislik
    $RALPH_BOS = 14;                 // rakamlarla Ralph arasi bosluk
    $EKRAN_G = $RAKAM_G + $RALPH_BOS + $RALPH_G + 16;
    $EKRAN_SOL = $PADDING;
    $EKRAN_UST = 42;                       // ustte baslik seridi
    $EKRAN_YUK = 92;                       // rakamlar + etiketler
    $TUGLA_YUK = 26;                       // Ralph'in kirdigi tugla sirasi
    $W = $EKRAN_G + $PADDING * 2;
    $H = $EKRAN_UST + $EKRAN_YUK + $TUGLA_YUK + $PADDING;
    // Rakamlarin dikey konumu ($RAKAM_YUK yukarida glif izgarasindan geldi)
    $RAKAM_UST = $EKRAN_UST + 16;
    $ETIKET_Y  = $RAKAM_UST + $RAKAM_YUK + 16;
    // ------------------------------------------------------------------
    // SEFFAF TEMA ICIN ISARET RENGI
    //
    // GIF'te saydamlik, "su renk saydam sayilsin" diye tek bir rengi
    // isaretleyerek olur. Dogrudan saydam cizemeyiz, cunku GIF'e
    // cevirirken kullanilan imagetruecolortopalette() saydamlik
    // bilgisini korumaz.
    //
    // Bu yuzden zemini once bu renkle boyuyoruz - tasarimda hic
    // gecmeyen, kimseye benzemeyen parlak bir ton. GIF'e yazarken de
    // "bu renk saydamdir" diye isaretliyoruz. Sonuc: forumun kendi
    // arka plani resmin altindan gorunuyor.
    // ------------------------------------------------------------------
    define('SEFFAF_R', 255);
    define('SEFFAF_G', 0);
    define('SEFFAF_B', 254);
    // ==================================================================
    // ARCADE PIKSEL FONT
    // Rakamlar 5x7 izgaraya cizilmis gercek bitmap glifler. Her "piksel"
    // ekranda $p x $p'lik bir blok olur - klasik arcade skor tabelasi gibi.
    // TTF'e bagli degil, her sunucuda birebir ayni gorunur.
    // ==================================================================
    /** 5x7 rakam glifleri. Her satir 5 karakter: '#' dolu, '.' bos. */
    function glif($ch)
    {
        static $font = [
            '0' => ['.###.',
                    '#...#',
                    '#..##',
                    '#.#.#',
                    '##..#',
                    '#...#',
                    '.###.'],
            '1' => ['..#..',
                    '.##..',
                    '..#..',
                    '..#..',
                    '..#..',
                    '..#..',
                    '.###.'],
            '2' => ['.###.',
                    '#...#',
                    '....#',
                    '...#.',
                    '..#..',
                    '.#...',
                    '#####'],
            '3' => ['#####',
                    '...#.',
                    '..#..',
                    '...#.',
                    '....#',
                    '#...#',
                    '.###.'],
            '4' => ['...#.',
                    '..##.',
                    '.#.#.',
                    '#..#.',
                    '#####',
                    '...#.',
                    '...#.'],
            '5' => ['#####',
                    '#....',
                    '####.',
                    '....#',
                    '....#',
                    '#...#',
                    '.###.'],
            '6' => ['..##.',
                    '.#...',
                    '#....',
                    '####.',
                    '#...#',
                    '#...#',
                    '.###.'],
            '7' => ['#####',
                    '....#',
                    '...#.',
                    '..#..',
                    '.#...',
                    '.#...',
                    '.#...'],
            '8' => ['.###.',
                    '#...#',
                    '#...#',
                    '.###.',
                    '#...#',
                    '#...#',
                    '.###.'],
            '9' => ['.###.',
                    '#...#',
                    '#...#',
                    '.####',
                    '....#',
                    '...#.',
                    '.##..'],
        ];
        return isset($font[$ch]) ? $font[$ch] : null;
    }
    /**
     * Tek bir rakami piksel bloklariyla cizer.
     *
     * $p       : bir glif pikselinin ekrandaki blok boyutu
     * $acik    : dolu piksel rengi
     * $govde   : blogun ust kenarinda kullanilan koyu ton (hacim hissi)
     */
    function rakamCiz($im, $rakam, $x, $y, $p, $acik, $govde)
    {
        $g = glif($rakam);
        if ($g === null) { return; }
        foreach ($g as $sy => $satir) {
            for ($sx = 0; $sx < 5; $sx++) {
                if ($satir[$sx] !== '#') { continue; }
                $x1 = $x + $sx * $p;
                $y1 = $y + $sy * $p;
                $x2 = $x1 + $p - 1;
                $y2 = $y1 + $p - 1;
                imagefilledrectangle($im, $x1, $y1, $x2, $y2, $acik);
                // Her blogun alt-sag kenarinda bir tik koyu ton: pikseller
                // birbirine yapismaz, izgara okunur kalir.
                imageline($im, $x1, $y2, $x2, $y2, $govde);
                imageline($im, $x2, $y1, $x2, $y2, $govde);
            }
        }
    }
    /** Iki nokta ayirac - glif izgarasiyla ayni blok boyutunda. */
    function ayiracCiz($im, $x, $y, $p, $renk, $govde)
    {
        foreach ([1, 4] as $sy) {
            $y1 = $y + $sy * $p + (int) round($p / 2);
            imagefilledrectangle($im, $x, $y1, $x + $p - 1, $y1 + $p - 1, $renk);
            imageline($im, $x, $y1 + $p - 1, $x + $p - 1, $y1 + $p - 1, $govde);
        }
    }
    // ==================================================================
    // CIZIM YARDIMCILARI
    // ==================================================================
    /** Yatayda ortalanmis metin. Harf araligi (tracking) destekli. */
    function yaz($im, $metin, $boyut, $merkezX, $baselineY, $renk, $font, $ttf, $aralik = 0)
    {
        if ($ttf && $aralik > 0) {
            // Harf harf cizip aralik veriyoruz - arcade etiketleri genis olur.
            $harfler = preg_split('//u', $metin, -1, PREG_SPLIT_NO_EMPTY);
            $toplam  = 0;
            $genler  = [];
            foreach ($harfler as $h) {
                $bb = imagettfbbox($boyut, 0, $font, $h);
                $g  = $bb[2] - $bb[0];
                $genler[] = $g;
                $toplam  += $g + $aralik;
            }
            $toplam -= $aralik;
            $x = (int) ($merkezX - $toplam / 2);
            foreach ($harfler as $i => $h) {
                imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $h);
                $x += $genler[$i] + $aralik;
            }
            return;
        }
        if ($ttf) {
            $bbox = imagettfbbox($boyut, 0, $font, $metin);
            $x = (int) ($merkezX - ($bbox[2] - $bbox[0]) / 2);
            imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $metin);
        } else {
            $f = $boyut >= 18 ? 5 : ($boyut >= 11 ? 3 : 2);
            // Dahili fontta aralik icin harf harf cizim
            if ($aralik > 0) {
                $bg     = imagefontwidth($f);
                $toplam = strlen($metin) * ($bg + $aralik) - $aralik;
                $x = (int) ($merkezX - $toplam / 2);
                $y = (int) ($baselineY - imagefontheight($f));
                for ($i = 0; $i < strlen($metin); $i++) {
                    imagestring($im, $f, $x, $y, $metin[$i], $renk);
                    $x += $bg + $aralik;
                }
                return;
            }
            $x = (int) ($merkezX - imagefontwidth($f) * strlen($metin) / 2);
            $y = (int) ($baselineY - imagefontheight($f));
            imagestring($im, $f, $x, $y, $metin, $renk);
        }
    }
    /** Belirli bir zaman icin hane metinlerini dondurur. */
    function degerler($kalan, $adet, $gunBasamak)
    {
        if ($kalan < 0) { $kalan = 0; }
        $v = [
            str_pad((string) (int) floor($kalan / 86400), $gunBasamak, '0', STR_PAD_LEFT),
            str_pad((string) (int) floor(($kalan % 86400) / 3600), 2, '0', STR_PAD_LEFT),
            str_pad((string) (int) floor(($kalan % 3600) / 60), 2, '0', STR_PAD_LEFT),
            str_pad((string) (int) ($kalan % 60), 2, '0', STR_PAD_LEFT),
        ];
        return array_slice($v, 0, $adet);
    }
    // ------------------------------------------------------------------
    // RALPH
    //
    // Karakteri hazir bir resimden almiyoruz, blok blok ciziyoruz.
    // Kocaman yumruklar, mavi tulum, dagink portakal sac.
    //
    // Asagidaki $b() yardimcisi isi kolaylastiriyor: koordinatlari
    // piksel yerine kucuk kareler halinde veriyoruz, o da $p ile
    // carpip ekrana ciziyor. Yani $b(3, 2, 5, 3) demek "soldan 3.,
    // yukaridan 2. kareden basla, 5 kare genis 3 kare yuksek bir
    // dikdortgen doldur" demek.
    // ------------------------------------------------------------------
    /**
     * Ralph'i cizer.
     *
     * $vurus yumrugun nerede oldugunu belirler:
     *   0 = kol yukarida (hazirlaniyor)
     *   1 = yumruk asagida (tuglaya indi)
     * Arasindaki degerler ara konumlari verir.
     */
    function ralphCiz($im, $x, $y, $p, $vurus, $renkler)
    {
        list($ten, $tulum, $sac, $koyu, $ayakkabi) = $renkler;
        $b = function ($gx, $gy, $gw, $gh, $renk) use ($im, $x, $y, $p) {
            imagefilledrectangle(
                $im,
                $x + $gx * $p,
                $y + $gy * $p,
                $x + ($gx + $gw) * $p - 1,
                $y + ($gy + $gh) * $p - 1,
                $renk
            );
        };
        // Vurusta govde one dogru cokuyor
        $eg = (int) round($vurus);
        // --- Sac (portakal, dagink) ---
        $b(3, 0 + $eg, 5, 1, $sac);
        $b(2, 1 + $eg, 7, 1, $sac);
        $b(2, 0 + $eg, 1, 1, $sac);       // dagink tutamlar
        $b(8, 0 + $eg, 1, 1, $sac);
        // --- Yuz ---
        $b(3, 2 + $eg, 5, 3, $ten);
        $b(3, 3 + $eg, 1, 1, $koyu);      // sol goz
        $b(6, 3 + $eg, 1, 1, $koyu);      // sag goz
        $b(4, 4 + $eg, 3, 1, $koyu);      // agiz - kararli ifade
        // --- Govde (tulum) ---
        $b(3, 5 + $eg, 5, 4, $tulum);
        $b(4, 5 + $eg, 1, 1, $koyu);      // tulum askilari
        $b(6, 5 + $eg, 1, 1, $koyu);
        // --- Sol kol + dev yumruk (sabit, yanda asili) ---
        $b(2, 6 + $eg, 1, 2, $ten);
        $b(0, 8 + $eg, 3, 3, $ten);       // Ralph'in meshur kocaman eli
        $b(0, 8 + $eg, 3, 1, $koyu);
        // --- Sag kol + dev yumruk: vurusa gore yukaridan tuglalara iner ---
        // $vurus 0 -> yumruk basin uzerinde, 1 -> yumruk zeminde
        $yumrukY = (int) round(-2 + $vurus * 11);
        // Omuzdan yumruga uzanan kol
        $omuzY = 6 + $eg;
        $ust   = min($omuzY, $yumrukY + $eg);
        $alt   = max($omuzY, $yumrukY + $eg + 2);
        $b(8, $ust, 1, max(1, $alt - $ust), $ten);
        // Yumruk (3x3 blok - govdeden buyuk, karakterin imzasi)
        $b(8, $yumrukY + $eg, 3, 3, $ten);
        $b(8, $yumrukY + $eg, 3, 1, $koyu);       // parmak eklemleri golgesi
        // --- Bacaklar ---
        $b(3, 9 + $eg, 2, 2, $tulum);
        $b(6, 9 + $eg, 2, 2, $tulum);
        $b(2, 11 + $eg, 3, 1, $ayakkabi);
        $b(6, 11 + $eg, 3, 1, $ayakkabi);
    }
    /**
     * Tugla sirasi. $kirikIndex >= 0 ise o tugla parcalanmis cizilir.
     */
    function tuglaCiz($im, $sol, $sag, $ust, $yuk, $kirikIndex, $renkler)
    {
        list($tugla, $ken, $toz) = $renkler;
        $tg = 26;                              // tugla genisligi
        $adet = (int) ceil(($sag - $sol) / $tg);
        for ($i = 0; $i < $adet; $i++) {
            $x1 = $sol + $i * $tg;
            $x2 = min($x1 + $tg - 2, $sag);
            if ($x1 >= $sag) { break; }
            if ($i === $kirikIndex) {
                // Parcalanmis tugla: ust yarisi ucmus, altta molozlar
                imagefilledrectangle($im, $x1, $ust + (int) ($yuk * 0.55), $x2, $ust + $yuk, $ken);
                // Ucusan parcalar
                imagefilledrectangle($im, $x1 + 3,  $ust - 5, $x1 + 7,  $ust - 1, $toz);
                imagefilledrectangle($im, $x1 + 13, $ust - 8, $x1 + 16, $ust - 5, $toz);
                imagefilledrectangle($im, $x2 - 5,  $ust - 3, $x2 - 2,  $ust,     $toz);
                continue;
            }
            imagefilledrectangle($im, $x1, $ust, $x2, $ust + $yuk, $tugla);
            imagerectangle($im, $x1, $ust, $x2, $ust + $yuk, $ken);
            // Ust kenar isigi
            imageline($im, $x1 + 1, $ust + 1, $x2 - 1, $ust + 1, $ken);
        }
    }
    // ==================================================================
    // KARE CIZIMI
    // ==================================================================
    /**
     * Tek bir kareyi cizer.
     *
     * @param int $kalan   Kalan saniye
     * @param int $faz     Animasyon fazi (0..N) - yumruk ve titresim icin
     */
    function kareCiz($kalan, $faz)
    {
        global $W, $H, $ADET, $PADDING, $PIX, $HANE_G, $HANE_BOS, $GRUP_BOS, $BASAMAKLAR,
               $EKRAN_SOL, $EKRAN_UST, $EKRAN_G, $EKRAN_YUK, $TUGLA_YUK,
               $RAKAM_UST, $RAKAM_YUK, $ETIKET_Y, $grupGen,
               $RALPH_P, $RALPH_G, $RALPH_BOS,
               $BASLIK, $BITTI_METNI, $ETIKETLER, $FONT, $ttf, $T, $tema, $gunBasamak;
        $im = imagecreatetruecolor($W, $H);
        imagealphablending($im, false);
        imagesavealpha($im, true);
        $r = function ($c, $a = 0) use ($im) {
            return imagecolorallocatealpha($im, $c[0], $c[1], $c[2], $a);
        };
        // ---------- ZEMIN ----------
        // Seffaf temada zemini isaret rengiyle boyuyoruz; o renk daha
        // sonra GIF'e yazilirken saydam olarak isaretleniyor.
        // (Ayrintili aciklama icin yukaridaki SEFFAF_R tanimina bakin.)
        if ($tema === 'seffaf') {
            imagefilledrectangle(
                $im, 0, 0, $W - 1, $H - 1,
                imagecolorallocate($im, SEFFAF_R, SEFFAF_G, SEFFAF_B)
            );
        } else {
            imagefilledrectangle($im, 0, 0, $W - 1, $H - 1, $r($T['zemin']));
        }
        imagealphablending($im, true);
        // ---------- DOLAP CERCEVESI ----------
        // Ekranin cevresinde ince cift cizgi - arcade dolabinin bezel'i.
        // Seffaf temada yari saydam tonlar nobetci rengi karistiracagi icin
        // tum cerceve ogeleri opak cizilir.
        $sf = ($tema === 'seffaf');
        $cerceve = $r($T['cerceve']);
        imagerectangle($im, 1, 1, $W - 2, $H - 2, $cerceve);
        imagerectangle($im, 3, 3, $W - 4, $H - 4, $sf ? $cerceve : $r($T['cerceve'], 60));
        // Kose vidalari (dolap detayi)
        $vida = $sf ? $r($T['baslik']) : $r($T['baslik'], 40);
        foreach ([[7, 7], [$W - 8, 7], [7, $H - 8], [$W - 8, $H - 8]] as $v) {
            imagefilledellipse($im, $v[0], $v[1], 3, 3, $vida);
        }
        // ---------- BASLIK SERIDI ----------
        // Marquee: dolabin ustundeki isikli tabela.
        // Seffaf temada opak cizilir - yoksa altindaki nobetci renk sizar.
        $mSol = $PADDING;
        $mSag = $W - $PADDING;
        if ($tema === 'acik') {
            // Acik temada tabela zeminle ayni tonda kalir, yazi koyu olur.
            imagefilledrectangle($im, $mSol, 10, $mSag, 30, $r($T['golge']));
        } else {
            imagefilledrectangle(
                $im, $mSol, 10, $mSag, 30,
                ($tema === 'seffaf') ? $r($T['ekran']) : $r($T['ekran'], 40)
            );
        }
        imagerectangle($im, $mSol, 10, $mSag, 30, $r($T['cerceve'], 50));
        yaz($im, $BASLIK, 8, $W / 2, 25, $r($T['marquee']), $FONT, $ttf, 2);
        // ---------- CRT EKRANI ----------
        $eSol = $EKRAN_SOL;
        $eSag = $W - $PADDING;
        $eUst = $EKRAN_UST;
        $eAlt = $H - $PADDING;
        imagefilledrectangle($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran']));
        // Ekran ici parlama: ustten asagi sonen ince cam yansimasi
        for ($i = 0; $i < 10; $i++) {
            imagefilledrectangle(
                $im,
                $eSol + 1, $eUst + 1 + $i,
                $eSag - 1, $eUst + 1 + $i,
                $r($T['siyan'], 116 + $i)
            );
        }
        // ---------- SURE DOLDU ----------
        // Ralph isini bitirmis: tuglalar yikilmis, molozun uzerinde duruyor.
        if ($kalan <= 0) {
            $tUstB = $eAlt - $TUGLA_YUK + 4;
            // Yikilmis tugla sirasi - hepsi kirik, yalnizca moloz kalmis
            $tg = 26;
            for ($bx = $eSol + 2; $bx < $eSag - 2; $bx += $tg) {
                $bx2 = min($bx + $tg - 2, $eSag - 2);
                $yh  = (int) (($TUGLA_YUK - 6) * 0.45);
                imagefilledrectangle(
                    $im, $bx, $tUstB + ($TUGLA_YUK - 6) - $yh, $bx2, $tUstB + $TUGLA_YUK - 6,
                    $r($T['tuglaKen'])
                );
            }
            yaz($im, $BITTI_METNI, 18, $W / 2, $eUst + 52, $r($T['magenta']), $FONT, $ttf, 4);
            // Ralph, molozun uzerinde - yumruk asagida, is bitmis
            ralphCiz(
                $im, $eSag - $RALPH_G - 6, $tUstB - 12 * $RALPH_P + 2, $RALPH_P, 1.0,
                [
                    $r($T['ten']), $r($T['tulum']), $r($T['amber']),
                    $r($T['mor']), $r($T['tuglaKen']),
                ]
            );
            tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
            return $im;
        }
        $vals = degerler($kalan, $ADET, $gunBasamak);
        // ---------- VURUS RITMI ----------
        // Ralph her saniye bir kez vuruyor: kareler sirayla "yumruk
        // asagida" ve "kol yukarida" diye gidiyor. Bu yuzden tek sayili
        // ve cift sayili kareler farkli cizilir.
        //
        // Rakamlarin kendisi sabit duruyor. Vurusta onlari da
        // titretmeyi denedim ama o zaman her karede butun rakam satiri
        // degismis sayiliyor ve dosya boyutu ucuyor. Hareket sadece
        // Ralph'ta ve tuglalarda kaliyor.
        $kirik = -1;
        $adim = $faz % 2;   // 0 = yumruk zeminde, 1 = kol yukarida
        if ($adim === 0) {
            $vurusFaz = 1.0;
            // Kirilan tuglayi her saniye degistiriyoruz ki Ralph hep
            // ayni yere vurmasin.
            $kirik = ((int) $kalan) % 9;
        } else {
            $vurusFaz = 0.15;
        }
        // ---------- RENKLER ----------
        $acik  = $r($T['siyan']);
        $govde = $r($T['mor'], 40);   // blok kenarlarindaki koyu ton
        $vurgu = $r($T['amber']);
        // Son 10 saniyede rakamlar pembeye doner - aciliyet hissi.
        if ($kalan <= 10) {
            $acik = $r($T['magenta']);
        }
        // ---------- RAKAMLARI YERLESTIR ----------
        // Rakamlar soldan basliyor; Ralph icin sagda yer ayrilmisti,
        // bu yuzden ikisi hicbir zaman cakismaz.
        $x  = $eSol + 10;
        $ry = $RAKAM_UST;
        foreach ($vals as $g => $deger) {
            $n  = $BASAMAKLAR[$g];
            $gy = $ry;
            for ($d = 0; $d < $n; $d++) {
                $hx = $x + $d * ($HANE_G + $HANE_BOS);
                rakamCiz($im, $deger[$d], $hx, $gy, $PIX, $acik, $govde);
            }
            // Etiket
            $grupMerkez = $x + $grupGen[$g] / 2;
            yaz($im, $ETIKETLER[$g], 7, $grupMerkez, $ETIKET_Y, $r($T['baslik']), $FONT, $ttf, 2);
            $x += $grupGen[$g];
            // Ayirac
            if ($g < $ADET - 1) {
                // Yanip sonme: vurus aninda parlak
                $aRenk = ($adim === 0) ? $vurgu : $r($T['amber'], 90);
                ayiracCiz($im, $x + (int) round(($GRUP_BOS - $PIX) / 2), $gy, $PIX, $aRenk, $govde);
                $x += $GRUP_BOS;
            }
        }
        // ---------- TUGLA SIRASI + RALPH ----------
        $tUst = $eAlt - $TUGLA_YUK + 4;
        tuglaCiz(
            $im, $eSol + 2, $eSag - 2, $tUst, $TUGLA_YUK - 6, $kirik,
            [$r($T['tugla']), $r($T['tuglaKen']), $r($T['amber'], 40)]
        );
        // Ralph rakamlarin sagindaki kendi seridinde, tuglalarin uzerinde durur
        $ralphP = $RALPH_P;
        $ralphX = $eSag - $RALPH_G - 6;
        $ralphY = $tUst - 12 * $ralphP + 2;
        ralphCiz(
            $im, $ralphX, $ralphY, $ralphP, $vurusFaz,
            [
                $r($T['ten']),
                $r($T['tulum']),
                [$r($T['amber'])][0],
                $r($T['mor']),
                $r($T['tuglaKen']),
            ]
        );
        // ---------- CRT TARAMA CIZGILERI ----------
        tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
        return $im;
    }
    /** CRT tarama cizgileri - her 3 pikselde bir yatay koyu cizgi. */
    function tarama($im, $sol, $ust, $sag, $alt, $renk)
    {
        for ($y = $ust; $y <= $alt; $y += 3) {
            imageline($im, $sol + 1, $y, $sag - 1, $y, $renk);
        }
    }
    // ==================================================================
    // GIF BIRLESTIRICI  —  isin can alici noktasi
    //
    // PHP'nin GD eklentisi animasyonlu GIF uretemez; imagegif() her
    // seferinde tek kareli bir GIF verir. Bu yuzden animasyonu elle
    // kuruyoruz:
    //
    //   1) Her kareyi ayri ayri cizip GD'ye tek kareli GIF yaptiriyoruz.
    //   2) O GIF'lerin icini acip sikistirilmis goruntu verisini ve
    //      renk paletini cekiyoruz (asagidaki gifCoz fonksiyonu).
    //   3) GIF89a dosya yapisini kendimiz yazip kareleri arka arkaya
    //      diziyoruz (en alttaki "GIF'I KUR" bolumu).
    //
    // Goruntu verisini yeniden sikistirmiyoruz; GD'nin sikistirdigi
    // hazir bloklari oldugu gibi tasiyoruz. Bu yuzden hizli calisir.
    // ==================================================================
    /**
     * Tek kareli bir GIF'in icinden renk paletini ve sikistirilmis
     * goruntu verisini cikarir. GIF dosyasi bloklardan olusur; basindan
     * baslayip blok blok ilerliyor, isimize yarayan yere gelince duruyoruz.
     */
    function gifCoz($bin)
    {
        $packed = ord($bin[10]);
        $p      = 13;
        $gct    = '';
        $gctBit = 0;
        if ($packed & 0x80) {
            $gctBit = $packed & 0x07;
            $len    = 3 * (1 << ($gctBit + 1));
            $gct    = substr($bin, $p, $len);
            $p     += $len;
        }
        $son  = strlen($bin);
        $seffafIdx = -1;
        while ($p < $son) {
            $b = ord($bin[$p]);
            if ($b === 0x21) {                       // uzanti blogu
                // Graphic Control Extension ise seffaf renk indeksini al
                if ($p + 1 < $son && ord($bin[$p + 1]) === 0xF9) {
                    $bayrak = ord($bin[$p + 3]);
                    if ($bayrak & 0x01) { $seffafIdx = ord($bin[$p + 6]); }
                }
                $p += 2;
                while ($p < $son && ($sz = ord($bin[$p])) !== 0) { $p += 1 + $sz; }
                $p++;
            } elseif ($b === 0x2C) {                 // goruntu tanimlayici
                $ipacked = ord($bin[$p + 9]);
                $p += 10;
                $pal = $gct;
                $bit = $gctBit;
                if ($ipacked & 0x80) {
                    $bit  = $ipacked & 0x07;
                    $len  = 3 * (1 << ($bit + 1));
                    $pal  = substr($bin, $p, $len);
                    $p   += $len;
                }
                $lzwMin = $bin[$p];
                $p++;
                $veri = '';
                while ($p < $son && ($sz = ord($bin[$p])) !== 0) {
                    $veri .= substr($bin, $p, 1 + $sz);
                    $p    += 1 + $sz;
                }
                $veri .= "\x00";
                return [
                    'pal'    => $pal,
                    'bit'    => $bit,
                    'min'    => $lzwMin,
                    'veri'   => $veri,
                    'seffaf' => $seffafIdx,
                ];
            } else {
                break;
            }
        }
        return null;
    }
    /**
     * Cizilmis bir resmi GIF verisine cevirir.
     *
     * $renkSayisi renk sayisini kisitlar (32, 64 gibi). Az renk =
     * kucuk dosya. Tasarimda zaten az renk kullandigimiz icin gozle
     * gorulur bir kayip olmuyor.
     *
     * Sira onemli: once renkler kisitlaniyor, SONRA saydam renk
     * isaretleniyor. Tersi yapilirsa saydamlik kayboluyor.
     */
    function gifBin($im, $renkSayisi = 0)
    {
        global $tema;
        if ($renkSayisi > 0) {
            imagetruecolortopalette($im, false, $renkSayisi);
        }
        if ($tema === 'seffaf') {
            // Isaret rengini palette buluyoruz. Renk kisitlama sirasinda
            // ton biraz kaymis olabilecegi icin en yakin esi ariyoruz.
            $idx = imagecolorclosest($im, SEFFAF_R, SEFFAF_G, SEFFAF_B);
            if ($idx >= 0) {
                imagecolortransparent($im, $idx);
            }
        }
        imagesavealpha($im, true);
        ob_start();
        imagegif($im);
        return ob_get_clean();
    }
    // ==================================================================
    // KARELERI URET
    //
    // Dosya boyutunu kucuk tutan numara burada. GIF'te her kareyi tam
    // boy gondermek zorunda degilsiniz; sadece degisen bolgeyi gonderip
    // "geri kalani oldugu gibi birak" diyebilirsiniz.
    //
    // Sayacta bir saniyede degisen sey cok az: bir iki rakam, yanip
    // sonen iki nokta ve Ralph. Ekranin geri kalani ayni kaliyor. Bu
    // yuzden her karede yalnizca degisen kucuk dikdortgenleri yaziyoruz.
    // Fark: 210 KB yerine 75 KB.
    // ==================================================================
    /**
     * Iki kareyi karsilastirip degisen bolgeleri dikdortgen listesi
     * olarak dondurur.
     *
     * Neden tek dikdortgen degil de liste? Cunku rakamlar solda, Ralph
     * sagda duruyor. Ikisini tek bir dikdortgene sigdirsaydik aradaki
     * kocaman degismemis alan da her karede bosuna gonderilirdi.
     * Onun yerine degisen sutunlari gruplayip her gruba kendi
     * dikdortgenini veriyoruz - GIF ayni karede birden fazla parcayi
     * zaten destekliyor.
     *
     * @return array  [[sol, ust, genislik, yukseklik], ...]
     *                Hicbir sey degismemisse bos dizi doner.
     */
    function farkKutulari($a, $b, $w, $h)
    {
        // Once degisen sutunlari ve satirlari isaretle
        $sutun = array_fill(0, $w, false);
        $ust = $h; $alt = -1;
        for ($y = 0; $y < $h; $y++) {
            $satirDegisti = false;
            for ($x = 0; $x < $w; $x++) {
                if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
                    $sutun[$x]    = true;
                    $satirDegisti = true;
                }
            }
            if ($satirDegisti) {
                if ($y < $ust) { $ust = $y; }
                $alt = $y;
            }
        }
        if ($alt < 0) { return []; }
        // Bitisik degisen sutunlari gruplara ayir.
        // Aralarinda $ESIK'ten az bos sutun varsa ayni gruba katilir - cok sayida
        // minik blok, palet yuku yuzunden tek bloktan pahali olurdu.
        $ESIK   = 24;
        $gruplar = [];
        $bas    = -1;
        $bosluk = 0;
        for ($x = 0; $x < $w; $x++) {
            if ($sutun[$x]) {
                if ($bas < 0) { $bas = $x; }
                $son    = $x;
                $bosluk = 0;
            } elseif ($bas >= 0) {
                $bosluk++;
                if ($bosluk > $ESIK) {
                    $gruplar[] = [$bas, $son];
                    $bas = -1;
                }
            }
        }
        if ($bas >= 0) { $gruplar[] = [$bas, $son]; }
        // Her grup icin dikey sinirlari daralt
        $kutular = [];
        foreach ($gruplar as $g) {
            list($gs, $gson) = $g;
            $gUst = $h; $gAlt = -1;
            for ($y = $ust; $y <= $alt; $y++) {
                for ($x = $gs; $x <= $gson; $x++) {
                    if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
                        if ($y < $gUst) { $gUst = $y; }
                        $gAlt = $y;
                        break;
                    }
                }
            }
            if ($gAlt < 0) { continue; }
            $kutular[] = [$gs, $gUst, $gson - $gs + 1, $gAlt - $gUst + 1];
        }
        return $kutular;
    }
    // Uretilen parcalar burada birikiyor. Her eleman:
    //   [sol, ust, goruntuVerisi, kareninSonuMu]
    $kareler     = [];
    $oncekiIm    = null;      // karsilastirma icin bir onceki kare
    $oncekiBitti = null;
    // Saniye saniye ilerleyip her an icin bir kare ciziyoruz.
    for ($i = 0; $i < $KARE_SAYISI; $i++) {
        $kalan = $hedefTs - ($simdi + $i);
        $bitti = ($kalan <= 0);
        // Sure dolduktan sonra ekran hic degismiyor; tek bir
        // "OYUN BITTI" karesi yeter, gerisini uretmiyoruz.
        if ($bitti && $oncekiBitti) { continue; }
        $im = kareCiz($kalan, $i);
        if ($oncekiIm === null) {
            // Ilk kare tam boy gonderilir - uzerine eklenecek bir sey
            // olmadigi icin zemin gorevi gorur.
            //
            // Kucuk bir tuzak: gifBin() resmin renklerini yerinde
            // degistirdigi icin, karsilastirmada kullanacagimiz temiz
            // kopyayi onceden almamiz gerekiyor. Yoksa bir sonraki
            // kareyle karsilastirdigimizda her piksel farkli cikardi.
            $kopya = imagecreatetruecolor($W, $H);
            imagealphablending($kopya, false);
            imagesavealpha($kopya, true);
            imagecopy($kopya, $im, 0, 0, 0, 0, $W, $H);
            $kareler[] = [0, 0, gifBin($im, 128), true];
            imagedestroy($im);
            $im = $kopya;
        } else {
            $kutular = farkKutulari($oncekiIm, $im, $W, $H);
            if (!$kutular) {
                // Hicbir sey degismedi - kareyi atla
                imagedestroy($im);
                continue;
            }
            $sonIdx = count($kutular) - 1;
            foreach ($kutular as $ki => $kutu) {
                list($sol, $ust, $gen, $yuk) = $kutu;
                $kirp = imagecreatetruecolor($gen, $yuk);
                imagealphablending($kirp, false);
                imagesavealpha($kirp, true);
                imagecopy($kirp, $im, 0, 0, $sol, $ust, $gen, $yuk);
                // Yalnizca son blok gecikme tasir; oncekiler ayni anda cizilir.
                $kareler[] = [$sol, $ust, gifBin($kirp, 32), $ki === $sonIdx];
                imagedestroy($kirp);
            }
        }
        if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
        $oncekiIm    = $im;
        $oncekiBitti = $bitti;
    }
    if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
    // ==================================================================
    // GIF'I KUR
    //
    // Elimizde kare kare goruntu parcalari var; simdi bunlari GIF89a
    // dosya yapisina gore arka arkaya diziyoruz. Sira sabittir:
    //   basik bilgi -> renk paleti -> dongu ayari -> kareler -> bitis
    // ==================================================================
    $ilk = gifCoz($kareler[0][2]);
    // --- Dosya basligi: imza, olculer, renk paleti ---
    $out  = "GIF89a";
    $out .= pack('v', $W) . pack('v', $H);
    $out .= chr(0x80 | 0x70 | $ilk['bit']) . "\x00\x00";
    $out .= $ilk['pal'];
    // --- Dongu ayari ---
    // Tarayicilara animasyonun kac kez oynayacagini soyleyen blok.
    // ($DONGU 1 ise bir kez oynar, 0 ise sonsuz doner.)
    $out .= "\x21\xFF\x0B" . "NETSCAPE2.0" . "\x03\x01" . pack('v', $DONGU) . "\x00";
    // --- Kareler ---
    foreach ($kareler as $idx => $k) {
        list($sol, $ust, $bin, $gecikmeVer) = $k;
        $f = gifCoz($bin);
        if ($f === null) { continue; }
        // Her parcanin onune bir kontrol blogu koyuyoruz. Burada iki
        // sey soyluyoruz: bu parca ne kadar ekranda kalacak ve
        // silinmeden yerinde birakilacak (silinseydi altindaki kare
        // kaybolur, ekran yanip sonerdi).
        //
        // Bir kareyi birden fazla parcaya bolmustuk. O parcalarin
        // yalnizca sonuncusuna bekleme suresi veriyoruz; oncekiler
        // 0 sureyle ard arda cizildigi icin izleyici tek kare gorur.
        $bayrak = (1 << 2);
        $seffafIdx = 0;
        if ($f['seffaf'] >= 0) {
            $bayrak |= 0x01;
            $seffafIdx = $f['seffaf'];
        }
        $out .= "\x21\xF9\x04" . chr($bayrak)
              . pack('v', $gecikmeVer ? $KARE_SURESI : 0)
              . chr($seffafIdx) . "\x00";
        // Parcanin ekranda nereye oturacagini ve olculerini yaziyoruz.
        // Tam boy olmadigi icin konum bilgisi onemli: tarayici bu
        // parcayi onceki karenin uzerine, tam bu noktaya cizecek.
        $gen = ord($bin[6]) | (ord($bin[7]) << 8);
        $yuk = ord($bin[8]) | (ord($bin[9]) << 8);
        $out .= "\x2C" . pack('v', $sol) . pack('v', $ust) . pack('v', $gen) . pack('v', $yuk);
        // Parcanin kendi renk paleti ve sikistirilmis goruntu verisi.
        $out .= chr(0x80 | $f['bit']) . $f['pal'];
        $out .= $f['min'] . $f['veri'];
    }
    // --- Dosya sonu isareti ---
    $out .= "\x3B";
    // ==================================================================
    // ONBELLEGE YAZ
    // ==================================================================
    if ($onbellekDosya !== null) {
        // Once gecici bir dosyaya yazip sonra asil adina tasiyoruz.
        // Boylece ayni anda gelen baska bir istek, yarim yazilmis
        // dosyayi okuyup bozuk resim gostermez.
        $gecici = $onbellekDosya . '.' . getmypid() . '.tmp';
        if (@file_put_contents($gecici, $out) === strlen($out)) {
            if (!@rename($gecici, $onbellekDosya)) { @unlink($gecici); }
        } else {
            // Yazamadiysak (izin yok, disk dolu) sorun degil; resim
            // yine gonderilir, sadece onbellekten yararlanilmaz.
            @unlink($gecici);
        }
        // Ayarlari her degistirdiginizde yeni bir dosya adi olusur ve
        // eskisi oldugu yerde kalir. Klasor sismesin diye ara sira
        // (yaklasik her 50 istekte bir) 1 saatten eski dosyalari
        // siliyoruz. Her istekte yapmak gereksiz yere disk yorardi.
        if (mt_rand(1, 50) === 1) {
            foreach ((array) @glob($ONBELLEK_DIZ . '/*.gif') as $eski) {
                if (time() - (int) @filemtime($eski) > 3600) { @unlink($eski); }
            }
        }
    }
    // ==================================================================
    // CIKTI
    // ==================================================================
    gifGonder($out);
  • 08-08-2026, 07:07:42
    #8
    bnsware adlı üyeden alıntı: mesajı görüntüle
    Elbette hocam, elimden geldiğince yorum satırlarıyla ne nedir, ne işe yarar anlatmaya çalıştım, spoilerden kodu görebilirsiniz

    <?php
    /**
     * ARCADE GERI SAYIM SAYACI  —  animasyonlu GIF
     * ---------------------------------------------------------------
     *
     * NEDEN GIF?
     * PNG durgun bir resimdir; sayfa yenilenmeden degismez. GIF ise
     * animasyon tasiyabildigi icin, sunucu istek geldigi anda 60
     * saniyelik bir animasyon uretir ve tarayici bunu kendi oynatir.
     * Sonuc: sayac sayfa yenilenmeden tiklar. Forumlar <img> etiketine
     * izin verdigi icin de konu icinde, imzada, her yerde calisir.
     *
     * KULLANIM
     *   HTML   : <img src="https://siteniz.com/index.php">
     *   BBCode : [img]https://siteniz.com/index.php[/img]
     *
     * TEMALAR (adresin sonuna eklenir)
     *   ?tema=koyu     koyu zemin (varsayilan)
     *   ?tema=acik     acik zemin
     *   ?tema=seffaf   zemin saydam; forum kendi arka planini gosterir,
     *                  yani hem koyu hem acik temali forumda uyar
     *
     * KURULUM
     *   1) Bu dosyayi sunucuya atin.
     *   2) Yanina "onbellek" adinda bos bir klasor acip yazma izni
     *      verin (755). Acmazsaniz yine calisir, sadece her istekte
     *      bastan uretir - yani daha yavas olur.
     *   3) Turkce karakter (GUN yerine GÜN) isterseniz ayni klasore
     *      "font.ttf" atin. Rakamlar fonta ihtiyac duymaz, onlari kod
     *      kendi cizer; font yalnizca alt etiketler icindir.
     *
     * GEREKSINIM
     *   PHP + GD eklentisi (imagegif). Baska kutuphane yok.
     *
     * NOT
     *   Animasyon 60 saniyeliktir; sonunda son karede durur. Sayfa
     *   yenilendiginde bastan uretilir. Daha uzun sursun isterseniz
     *   asagidaki $KARE_SAYISI degerini buyutun (dosya boyutu artar).
     */
    // ==================================================================
    // AYARLAR  —  duzenlemeniz gereken tek bolum burasi
    // ==================================================================
    date_default_timezone_set('Europe/Istanbul');
    // Geri sayimin bitecegi an. Bicim: 'YIL-AY-GUN SAAT:DAKIKA:SANIYE'
    $HEDEF       = '2026-09-15 14:13:00';
    // Sayacin ustundeki tabelada yazan metin
    $BASLIK      = 'UGILABS - GIF Versiyon';
    // Sure doldugunda ekranda yazacak metin
    $BITTI_METNI = 'OYUN BITTI';
    // ------------------------------------------------------------------
    // ANIMASYON
    // ------------------------------------------------------------------
    // Kac saniyelik animasyon uretilsin. 60 = 1 dakika.
    // Buyutursen daha uzun sure tiklar ama dosya buyur:
    //   60 kare  ~75 KB  (1 dakika)
    //  300 kare ~250 KB  (5 dakika)
    $KARE_SAYISI  = 60;
    // Her karenin ekranda kalma suresi (1/100 saniye cinsinden).
    // 100 = 1 saniye. Sayac saniye saydigi icin bunu degistirme.
    $KARE_SURESI  = 100;
    // 1 = bir kez oynar, sonra son karede durur.
    // 0 = sonsuz doner. Surekli hareket eder ama her turda basa
    //     saracagi icin yanlis zaman gostermeye baslar. Sayac icin
    //     1 dogrusu; sonsuz dongu ancak susleme amacli isine yarar.
    $DONGU        = 1;
    // false yaparsan saniye hanesi kaybolur, sadece gun/saat/dakika
    // kalir. O durumda animasyona gerek kalmaz, tek kare uretilir.
    $SANIYE_GOSTER = true;
    // ------------------------------------------------------------------
    // ONBELLEK  —  paylasimli hostingde sunucuyu ayakta tutar
    //
    // Her istekte animasyonu bastan cizmek sunucuyu yorar (~0.8 sn).
    // Populer bir konuda 50 kisi ayni anda sayfayi acarsa bu 40 saniye
    // islemci demektir ve paylasimli hosting bunu kaldirmaz.
    //
    // Bu yuzden uretilen GIF diske yazilir; asagidaki sure boyunca
    // gelen istekler yeniden cizmeden hazir dosyayi alir. 50 kisi ayni
    // anda girse bile tek uretim yapilir.
    //
    // Sayacin dogrulugunu bozmaz: en fazla bu sure kadar geriden gelir,
    // yani 3 saniyede kimsenin fark edecegi bir sapma olusmaz.
    // ------------------------------------------------------------------
    // Uretilen dosya kac saniye saklansin. 0 yazarsan onbellek kapanir.
    $ONBELLEK_SN  = 3;
    // Dosyalarin yazilacagi klasor. Yazma izni yoksa sistem yine
    // calisir, sadece her istekte bastan uretir.
    $ONBELLEK_DIZ = __DIR__ . '/onbellek';
    // ------------------------------------------------------------------
    // FONT
    // Rakamlar fonta ihtiyac duymaz - onlari kod nokta nokta cizer.
    // Buradaki font yalnizca alt etiketler (GUN, SAAT...) ve baslik
    // icindir. Turkce karakter isterseniz klasore font.ttf atin;
    // bulunamazsa asagidaki sistem fontlari sirayla denenir.
    // ------------------------------------------------------------------
    $FONT_ADAYLARI = [
        __DIR__ . '/font.ttf',
        'C:/Windows/Fonts/consolab.ttf',
        '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
        '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
    ];
    // ------------------------------------------------------------------
    // TEMA  —  arcade dolabinin ekrani
    // ------------------------------------------------------------------
    $tema = isset($_GET['tema']) ? strtolower($_GET['tema']) : 'koyu';
    if (!in_array($tema, ['koyu', 'acik', 'seffaf'], true)) { $tema = 'koyu'; }
    $T = [
        // Ortak arcade paleti - zeminden bagimsiz, her iki temada da okunur.
        'siyan'    => [ 34, 224, 232],
        'magenta'  => [255,  61, 139],
        'amber'    => [255, 197,  61],
        'mor'      => [ 27,  16,  53],
        'ten'      => [217, 142,  95],
        'tulum'    => [ 46,  92, 138],
        'tugla'    => [166,  74,  58],
        'tuglaKen' => [ 92,  38,  30],
    ];
    // 'ekran' her temada koyu kalir - CRT camı zeminden bagimsizdir.
    // 'baslik'  : ekranin ICINDEKI etiketler (hep koyu zemin uzerinde)
    // 'marquee' : ekranin USTUNDEKI tabela yazisi (zemine gore degisir)
    if ($tema === 'acik') {
        $T['zemin']   = [232, 230, 240];
        $T['ekran']   = [ 38,  24,  70];
        $T['cerceve'] = [ 62,  44, 104];
        $T['baslik']  = [166, 152, 208];
        $T['marquee'] = [ 44,  30,  84];   // acik zeminde koyu yazi
        $T['golge']   = [206, 202, 220];
    } else {
        $T['zemin']   = [ 16,  10,  30];
        $T['ekran']   = [ 30,  18,  58];
        $T['cerceve'] = [ 62,  44, 104];
        $T['baslik']  = [148, 132, 196];
        $T['marquee'] = [172, 156, 216];
        $T['golge']   = [ 10,   6,  20];
    }
    // ==================================================================
    // ONBELLEKTEN SUNMA
    //
    // Bu bolum dosyanin en basinda duruyor cunku amaci cizim islerine
    // hic girmeden isi bitirmek. Yeterince taze bir dosya varsa onu
    // gonderip cikiyoruz; asagidaki kodun tamami hic calismiyor.
    // ==================================================================
    /** Hazir GIF verisini tarayiciya gonderir ve betigi sonlandirir. */
    function gifGonder($veri)
    {
        header('Content-Type: image/gif');
        header('Content-Length: ' . strlen($veri));
        // Tarayiciya "bu resmi saklama" diyoruz. Saklarsa sayfa
        // yenilendiginde eski animasyonu gosterir ve sayac yanlis
        // zamanda kalir.
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');
        header('Expires: Sat, 01 Jan 2000 00:00:00 GMT');
        echo $veri;
        exit;
    }
    $onbellekDosya = null;
    if ($ONBELLEK_SN > 0) {
        // Dosya adini ayarlardan uretiyoruz: ayni ayarlar ayni dosyayi
        // kullanir, farkli tema kendi dosyasini alir. Boylece temalar
        // birbirine karismaz.
        $anahtar = md5(implode('|', [
            $HEDEF, $BASLIK, $BITTI_METNI, $tema,
            $KARE_SAYISI, $KARE_SURESI, $DONGU, $SANIYE_GOSTER,
        ]));
        if (!is_dir($ONBELLEK_DIZ)) { @mkdir($ONBELLEK_DIZ, 0755, true); }
        $onbellekDosya = $ONBELLEK_DIZ . '/' . $anahtar . '.gif';
        // Dosya var ve yeterince taze mi? Oyleyse hic cizmeden gonder.
        if (is_readable($onbellekDosya)) {
            $yas = time() - (int) @filemtime($onbellekDosya);
            if ($yas >= 0 && $yas < $ONBELLEK_SN) {
                $hazir = @file_get_contents($onbellekDosya);
                if ($hazir !== false && strlen($hazir) > 0) {
                    gifGonder($hazir);
                }
            }
        }
        // Buraya dustuyse dosya yok ya da eskimis; asagida yeniden
        // uretilip en sonda diske yazilacak.
    }
    // ==================================================================
    // HAZIRLIK
    // ==================================================================
    $FONT = null;
    foreach ($FONT_ADAYLARI as $f) {
        if (is_readable($f)) { $FONT = $f; break; }
    }
    $ttf = ($FONT !== null);
    $hedefTs = strtotime($HEDEF);
    $simdi   = time();
    if (!$SANIYE_GOSTER) { $KARE_SAYISI = 1; }
    $ETIKETLER = ['GUN', 'SAAT', 'DAKIKA', 'SANIYE'];
    $ADET      = $SANIYE_GOSTER ? 4 : 3;
    // ------------------------------------------------------------------
    // OLCULER
    // Ekran bir arcade dolabinin CRT'si: disinda cerceve, icinde rakamlar,
    // en altta Ralph'in yumruk attigi tugla siras var.
    // ------------------------------------------------------------------
    $PADDING   = 14;   // dis cerceve kalinligi
    // Rakamlar 5x7 glif izgarasi. $PIX bir glif pikselinin blok boyutu.
    $PIX       = 6;
    $HANE_G    = 5 * $PIX;          // bir rakam hanesinin genisligi
    $RAKAM_YUK = 7 * $PIX;          // glif yuksekligi
    $HANE_BOS  = $PIX;              // ayni grup icindeki haneler arasi
    $GRUP_BOS  = $PIX * 3;          // gruplar arasi (iki nokta buraya gelir)
    // Gun hanesi 3 basamak olabilir (999 gun), digerleri hep 2 basamak.
    $gunBasamak = max(2, strlen((string) (int) floor(max(0, $hedefTs - $simdi) / 86400)));
    $BASAMAKLAR = [$gunBasamak, 2, 2, 2];
    $BASAMAKLAR = array_slice($BASAMAKLAR, 0, $ADET);
    $grupGen = [];
    foreach ($BASAMAKLAR as $n) {
        $grupGen[] = $n * $HANE_G + ($n - 1) * $HANE_BOS;
    }
    // Rakam bloklarinin toplam genisligi
    $RAKAM_G = array_sum($grupGen) + ($ADET - 1) * $GRUP_BOS;
    // Ralph rakamlarin SAGINDA kendi seridinde durur - etiketleri kapatmaz.
    $RALPH_P   = 3;                  // Ralph'in piksel blok boyutu
    $RALPH_G   = 13 * $RALPH_P;      // Ralph + yumruk icin ayrilan genislik
    $RALPH_BOS = 14;                 // rakamlarla Ralph arasi bosluk
    $EKRAN_G = $RAKAM_G + $RALPH_BOS + $RALPH_G + 16;
    $EKRAN_SOL = $PADDING;
    $EKRAN_UST = 42;                       // ustte baslik seridi
    $EKRAN_YUK = 92;                       // rakamlar + etiketler
    $TUGLA_YUK = 26;                       // Ralph'in kirdigi tugla sirasi
    $W = $EKRAN_G + $PADDING * 2;
    $H = $EKRAN_UST + $EKRAN_YUK + $TUGLA_YUK + $PADDING;
    // Rakamlarin dikey konumu ($RAKAM_YUK yukarida glif izgarasindan geldi)
    $RAKAM_UST = $EKRAN_UST + 16;
    $ETIKET_Y  = $RAKAM_UST + $RAKAM_YUK + 16;
    // ------------------------------------------------------------------
    // SEFFAF TEMA ICIN ISARET RENGI
    //
    // GIF'te saydamlik, "su renk saydam sayilsin" diye tek bir rengi
    // isaretleyerek olur. Dogrudan saydam cizemeyiz, cunku GIF'e
    // cevirirken kullanilan imagetruecolortopalette() saydamlik
    // bilgisini korumaz.
    //
    // Bu yuzden zemini once bu renkle boyuyoruz - tasarimda hic
    // gecmeyen, kimseye benzemeyen parlak bir ton. GIF'e yazarken de
    // "bu renk saydamdir" diye isaretliyoruz. Sonuc: forumun kendi
    // arka plani resmin altindan gorunuyor.
    // ------------------------------------------------------------------
    define('SEFFAF_R', 255);
    define('SEFFAF_G', 0);
    define('SEFFAF_B', 254);
    // ==================================================================
    // ARCADE PIKSEL FONT
    // Rakamlar 5x7 izgaraya cizilmis gercek bitmap glifler. Her "piksel"
    // ekranda $p x $p'lik bir blok olur - klasik arcade skor tabelasi gibi.
    // TTF'e bagli degil, her sunucuda birebir ayni gorunur.
    // ==================================================================
    /** 5x7 rakam glifleri. Her satir 5 karakter: '#' dolu, '.' bos. */
    function glif($ch)
    {
        static $font = [
            '0' => ['.###.',
                    '#...#',
                    '#..##',
                    '#.#.#',
                    '##..#',
                    '#...#',
                    '.###.'],
            '1' => ['..#..',
                    '.##..',
                    '..#..',
                    '..#..',
                    '..#..',
                    '..#..',
                    '.###.'],
            '2' => ['.###.',
                    '#...#',
                    '....#',
                    '...#.',
                    '..#..',
                    '.#...',
                    '#####'],
            '3' => ['#####',
                    '...#.',
                    '..#..',
                    '...#.',
                    '....#',
                    '#...#',
                    '.###.'],
            '4' => ['...#.',
                    '..##.',
                    '.#.#.',
                    '#..#.',
                    '#####',
                    '...#.',
                    '...#.'],
            '5' => ['#####',
                    '#....',
                    '####.',
                    '....#',
                    '....#',
                    '#...#',
                    '.###.'],
            '6' => ['..##.',
                    '.#...',
                    '#....',
                    '####.',
                    '#...#',
                    '#...#',
                    '.###.'],
            '7' => ['#####',
                    '....#',
                    '...#.',
                    '..#..',
                    '.#...',
                    '.#...',
                    '.#...'],
            '8' => ['.###.',
                    '#...#',
                    '#...#',
                    '.###.',
                    '#...#',
                    '#...#',
                    '.###.'],
            '9' => ['.###.',
                    '#...#',
                    '#...#',
                    '.####',
                    '....#',
                    '...#.',
                    '.##..'],
        ];
        return isset($font[$ch]) ? $font[$ch] : null;
    }
    /**
     * Tek bir rakami piksel bloklariyla cizer.
     *
     * $p       : bir glif pikselinin ekrandaki blok boyutu
     * $acik    : dolu piksel rengi
     * $govde   : blogun ust kenarinda kullanilan koyu ton (hacim hissi)
     */
    function rakamCiz($im, $rakam, $x, $y, $p, $acik, $govde)
    {
        $g = glif($rakam);
        if ($g === null) { return; }
        foreach ($g as $sy => $satir) {
            for ($sx = 0; $sx < 5; $sx++) {
                if ($satir[$sx] !== '#') { continue; }
                $x1 = $x + $sx * $p;
                $y1 = $y + $sy * $p;
                $x2 = $x1 + $p - 1;
                $y2 = $y1 + $p - 1;
                imagefilledrectangle($im, $x1, $y1, $x2, $y2, $acik);
                // Her blogun alt-sag kenarinda bir tik koyu ton: pikseller
                // birbirine yapismaz, izgara okunur kalir.
                imageline($im, $x1, $y2, $x2, $y2, $govde);
                imageline($im, $x2, $y1, $x2, $y2, $govde);
            }
        }
    }
    /** Iki nokta ayirac - glif izgarasiyla ayni blok boyutunda. */
    function ayiracCiz($im, $x, $y, $p, $renk, $govde)
    {
        foreach ([1, 4] as $sy) {
            $y1 = $y + $sy * $p + (int) round($p / 2);
            imagefilledrectangle($im, $x, $y1, $x + $p - 1, $y1 + $p - 1, $renk);
            imageline($im, $x, $y1 + $p - 1, $x + $p - 1, $y1 + $p - 1, $govde);
        }
    }
    // ==================================================================
    // CIZIM YARDIMCILARI
    // ==================================================================
    /** Yatayda ortalanmis metin. Harf araligi (tracking) destekli. */
    function yaz($im, $metin, $boyut, $merkezX, $baselineY, $renk, $font, $ttf, $aralik = 0)
    {
        if ($ttf && $aralik > 0) {
            // Harf harf cizip aralik veriyoruz - arcade etiketleri genis olur.
            $harfler = preg_split('//u', $metin, -1, PREG_SPLIT_NO_EMPTY);
            $toplam  = 0;
            $genler  = [];
            foreach ($harfler as $h) {
                $bb = imagettfbbox($boyut, 0, $font, $h);
                $g  = $bb[2] - $bb[0];
                $genler[] = $g;
                $toplam  += $g + $aralik;
            }
            $toplam -= $aralik;
            $x = (int) ($merkezX - $toplam / 2);
            foreach ($harfler as $i => $h) {
                imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $h);
                $x += $genler[$i] + $aralik;
            }
            return;
        }
        if ($ttf) {
            $bbox = imagettfbbox($boyut, 0, $font, $metin);
            $x = (int) ($merkezX - ($bbox[2] - $bbox[0]) / 2);
            imagettftext($im, $boyut, 0, $x, $baselineY, $renk, $font, $metin);
        } else {
            $f = $boyut >= 18 ? 5 : ($boyut >= 11 ? 3 : 2);
            // Dahili fontta aralik icin harf harf cizim
            if ($aralik > 0) {
                $bg     = imagefontwidth($f);
                $toplam = strlen($metin) * ($bg + $aralik) - $aralik;
                $x = (int) ($merkezX - $toplam / 2);
                $y = (int) ($baselineY - imagefontheight($f));
                for ($i = 0; $i < strlen($metin); $i++) {
                    imagestring($im, $f, $x, $y, $metin[$i], $renk);
                    $x += $bg + $aralik;
                }
                return;
            }
            $x = (int) ($merkezX - imagefontwidth($f) * strlen($metin) / 2);
            $y = (int) ($baselineY - imagefontheight($f));
            imagestring($im, $f, $x, $y, $metin, $renk);
        }
    }
    /** Belirli bir zaman icin hane metinlerini dondurur. */
    function degerler($kalan, $adet, $gunBasamak)
    {
        if ($kalan < 0) { $kalan = 0; }
        $v = [
            str_pad((string) (int) floor($kalan / 86400), $gunBasamak, '0', STR_PAD_LEFT),
            str_pad((string) (int) floor(($kalan % 86400) / 3600), 2, '0', STR_PAD_LEFT),
            str_pad((string) (int) floor(($kalan % 3600) / 60), 2, '0', STR_PAD_LEFT),
            str_pad((string) (int) ($kalan % 60), 2, '0', STR_PAD_LEFT),
        ];
        return array_slice($v, 0, $adet);
    }
    // ------------------------------------------------------------------
    // RALPH
    //
    // Karakteri hazir bir resimden almiyoruz, blok blok ciziyoruz.
    // Kocaman yumruklar, mavi tulum, dagink portakal sac.
    //
    // Asagidaki $b() yardimcisi isi kolaylastiriyor: koordinatlari
    // piksel yerine kucuk kareler halinde veriyoruz, o da $p ile
    // carpip ekrana ciziyor. Yani $b(3, 2, 5, 3) demek "soldan 3.,
    // yukaridan 2. kareden basla, 5 kare genis 3 kare yuksek bir
    // dikdortgen doldur" demek.
    // ------------------------------------------------------------------
    /**
     * Ralph'i cizer.
     *
     * $vurus yumrugun nerede oldugunu belirler:
     *   0 = kol yukarida (hazirlaniyor)
     *   1 = yumruk asagida (tuglaya indi)
     * Arasindaki degerler ara konumlari verir.
     */
    function ralphCiz($im, $x, $y, $p, $vurus, $renkler)
    {
        list($ten, $tulum, $sac, $koyu, $ayakkabi) = $renkler;
        $b = function ($gx, $gy, $gw, $gh, $renk) use ($im, $x, $y, $p) {
            imagefilledrectangle(
                $im,
                $x + $gx * $p,
                $y + $gy * $p,
                $x + ($gx + $gw) * $p - 1,
                $y + ($gy + $gh) * $p - 1,
                $renk
            );
        };
        // Vurusta govde one dogru cokuyor
        $eg = (int) round($vurus);
        // --- Sac (portakal, dagink) ---
        $b(3, 0 + $eg, 5, 1, $sac);
        $b(2, 1 + $eg, 7, 1, $sac);
        $b(2, 0 + $eg, 1, 1, $sac);       // dagink tutamlar
        $b(8, 0 + $eg, 1, 1, $sac);
        // --- Yuz ---
        $b(3, 2 + $eg, 5, 3, $ten);
        $b(3, 3 + $eg, 1, 1, $koyu);      // sol goz
        $b(6, 3 + $eg, 1, 1, $koyu);      // sag goz
        $b(4, 4 + $eg, 3, 1, $koyu);      // agiz - kararli ifade
        // --- Govde (tulum) ---
        $b(3, 5 + $eg, 5, 4, $tulum);
        $b(4, 5 + $eg, 1, 1, $koyu);      // tulum askilari
        $b(6, 5 + $eg, 1, 1, $koyu);
        // --- Sol kol + dev yumruk (sabit, yanda asili) ---
        $b(2, 6 + $eg, 1, 2, $ten);
        $b(0, 8 + $eg, 3, 3, $ten);       // Ralph'in meshur kocaman eli
        $b(0, 8 + $eg, 3, 1, $koyu);
        // --- Sag kol + dev yumruk: vurusa gore yukaridan tuglalara iner ---
        // $vurus 0 -> yumruk basin uzerinde, 1 -> yumruk zeminde
        $yumrukY = (int) round(-2 + $vurus * 11);
        // Omuzdan yumruga uzanan kol
        $omuzY = 6 + $eg;
        $ust   = min($omuzY, $yumrukY + $eg);
        $alt   = max($omuzY, $yumrukY + $eg + 2);
        $b(8, $ust, 1, max(1, $alt - $ust), $ten);
        // Yumruk (3x3 blok - govdeden buyuk, karakterin imzasi)
        $b(8, $yumrukY + $eg, 3, 3, $ten);
        $b(8, $yumrukY + $eg, 3, 1, $koyu);       // parmak eklemleri golgesi
        // --- Bacaklar ---
        $b(3, 9 + $eg, 2, 2, $tulum);
        $b(6, 9 + $eg, 2, 2, $tulum);
        $b(2, 11 + $eg, 3, 1, $ayakkabi);
        $b(6, 11 + $eg, 3, 1, $ayakkabi);
    }
    /**
     * Tugla sirasi. $kirikIndex >= 0 ise o tugla parcalanmis cizilir.
     */
    function tuglaCiz($im, $sol, $sag, $ust, $yuk, $kirikIndex, $renkler)
    {
        list($tugla, $ken, $toz) = $renkler;
        $tg = 26;                              // tugla genisligi
        $adet = (int) ceil(($sag - $sol) / $tg);
        for ($i = 0; $i < $adet; $i++) {
            $x1 = $sol + $i * $tg;
            $x2 = min($x1 + $tg - 2, $sag);
            if ($x1 >= $sag) { break; }
            if ($i === $kirikIndex) {
                // Parcalanmis tugla: ust yarisi ucmus, altta molozlar
                imagefilledrectangle($im, $x1, $ust + (int) ($yuk * 0.55), $x2, $ust + $yuk, $ken);
                // Ucusan parcalar
                imagefilledrectangle($im, $x1 + 3,  $ust - 5, $x1 + 7,  $ust - 1, $toz);
                imagefilledrectangle($im, $x1 + 13, $ust - 8, $x1 + 16, $ust - 5, $toz);
                imagefilledrectangle($im, $x2 - 5,  $ust - 3, $x2 - 2,  $ust,     $toz);
                continue;
            }
            imagefilledrectangle($im, $x1, $ust, $x2, $ust + $yuk, $tugla);
            imagerectangle($im, $x1, $ust, $x2, $ust + $yuk, $ken);
            // Ust kenar isigi
            imageline($im, $x1 + 1, $ust + 1, $x2 - 1, $ust + 1, $ken);
        }
    }
    // ==================================================================
    // KARE CIZIMI
    // ==================================================================
    /**
     * Tek bir kareyi cizer.
     *
     * @param int $kalan   Kalan saniye
     * @param int $faz     Animasyon fazi (0..N) - yumruk ve titresim icin
     */
    function kareCiz($kalan, $faz)
    {
        global $W, $H, $ADET, $PADDING, $PIX, $HANE_G, $HANE_BOS, $GRUP_BOS, $BASAMAKLAR,
               $EKRAN_SOL, $EKRAN_UST, $EKRAN_G, $EKRAN_YUK, $TUGLA_YUK,
               $RAKAM_UST, $RAKAM_YUK, $ETIKET_Y, $grupGen,
               $RALPH_P, $RALPH_G, $RALPH_BOS,
               $BASLIK, $BITTI_METNI, $ETIKETLER, $FONT, $ttf, $T, $tema, $gunBasamak;
        $im = imagecreatetruecolor($W, $H);
        imagealphablending($im, false);
        imagesavealpha($im, true);
        $r = function ($c, $a = 0) use ($im) {
            return imagecolorallocatealpha($im, $c[0], $c[1], $c[2], $a);
        };
        // ---------- ZEMIN ----------
        // Seffaf temada zemini isaret rengiyle boyuyoruz; o renk daha
        // sonra GIF'e yazilirken saydam olarak isaretleniyor.
        // (Ayrintili aciklama icin yukaridaki SEFFAF_R tanimina bakin.)
        if ($tema === 'seffaf') {
            imagefilledrectangle(
                $im, 0, 0, $W - 1, $H - 1,
                imagecolorallocate($im, SEFFAF_R, SEFFAF_G, SEFFAF_B)
            );
        } else {
            imagefilledrectangle($im, 0, 0, $W - 1, $H - 1, $r($T['zemin']));
        }
        imagealphablending($im, true);
        // ---------- DOLAP CERCEVESI ----------
        // Ekranin cevresinde ince cift cizgi - arcade dolabinin bezel'i.
        // Seffaf temada yari saydam tonlar nobetci rengi karistiracagi icin
        // tum cerceve ogeleri opak cizilir.
        $sf = ($tema === 'seffaf');
        $cerceve = $r($T['cerceve']);
        imagerectangle($im, 1, 1, $W - 2, $H - 2, $cerceve);
        imagerectangle($im, 3, 3, $W - 4, $H - 4, $sf ? $cerceve : $r($T['cerceve'], 60));
        // Kose vidalari (dolap detayi)
        $vida = $sf ? $r($T['baslik']) : $r($T['baslik'], 40);
        foreach ([[7, 7], [$W - 8, 7], [7, $H - 8], [$W - 8, $H - 8]] as $v) {
            imagefilledellipse($im, $v[0], $v[1], 3, 3, $vida);
        }
        // ---------- BASLIK SERIDI ----------
        // Marquee: dolabin ustundeki isikli tabela.
        // Seffaf temada opak cizilir - yoksa altindaki nobetci renk sizar.
        $mSol = $PADDING;
        $mSag = $W - $PADDING;
        if ($tema === 'acik') {
            // Acik temada tabela zeminle ayni tonda kalir, yazi koyu olur.
            imagefilledrectangle($im, $mSol, 10, $mSag, 30, $r($T['golge']));
        } else {
            imagefilledrectangle(
                $im, $mSol, 10, $mSag, 30,
                ($tema === 'seffaf') ? $r($T['ekran']) : $r($T['ekran'], 40)
            );
        }
        imagerectangle($im, $mSol, 10, $mSag, 30, $r($T['cerceve'], 50));
        yaz($im, $BASLIK, 8, $W / 2, 25, $r($T['marquee']), $FONT, $ttf, 2);
        // ---------- CRT EKRANI ----------
        $eSol = $EKRAN_SOL;
        $eSag = $W - $PADDING;
        $eUst = $EKRAN_UST;
        $eAlt = $H - $PADDING;
        imagefilledrectangle($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran']));
        // Ekran ici parlama: ustten asagi sonen ince cam yansimasi
        for ($i = 0; $i < 10; $i++) {
            imagefilledrectangle(
                $im,
                $eSol + 1, $eUst + 1 + $i,
                $eSag - 1, $eUst + 1 + $i,
                $r($T['siyan'], 116 + $i)
            );
        }
        // ---------- SURE DOLDU ----------
        // Ralph isini bitirmis: tuglalar yikilmis, molozun uzerinde duruyor.
        if ($kalan <= 0) {
            $tUstB = $eAlt - $TUGLA_YUK + 4;
            // Yikilmis tugla sirasi - hepsi kirik, yalnizca moloz kalmis
            $tg = 26;
            for ($bx = $eSol + 2; $bx < $eSag - 2; $bx += $tg) {
                $bx2 = min($bx + $tg - 2, $eSag - 2);
                $yh  = (int) (($TUGLA_YUK - 6) * 0.45);
                imagefilledrectangle(
                    $im, $bx, $tUstB + ($TUGLA_YUK - 6) - $yh, $bx2, $tUstB + $TUGLA_YUK - 6,
                    $r($T['tuglaKen'])
                );
            }
            yaz($im, $BITTI_METNI, 18, $W / 2, $eUst + 52, $r($T['magenta']), $FONT, $ttf, 4);
            // Ralph, molozun uzerinde - yumruk asagida, is bitmis
            ralphCiz(
                $im, $eSag - $RALPH_G - 6, $tUstB - 12 * $RALPH_P + 2, $RALPH_P, 1.0,
                [
                    $r($T['ten']), $r($T['tulum']), $r($T['amber']),
                    $r($T['mor']), $r($T['tuglaKen']),
                ]
            );
            tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
            return $im;
        }
        $vals = degerler($kalan, $ADET, $gunBasamak);
        // ---------- VURUS RITMI ----------
        // Ralph her saniye bir kez vuruyor: kareler sirayla "yumruk
        // asagida" ve "kol yukarida" diye gidiyor. Bu yuzden tek sayili
        // ve cift sayili kareler farkli cizilir.
        //
        // Rakamlarin kendisi sabit duruyor. Vurusta onlari da
        // titretmeyi denedim ama o zaman her karede butun rakam satiri
        // degismis sayiliyor ve dosya boyutu ucuyor. Hareket sadece
        // Ralph'ta ve tuglalarda kaliyor.
        $kirik = -1;
        $adim = $faz % 2;   // 0 = yumruk zeminde, 1 = kol yukarida
        if ($adim === 0) {
            $vurusFaz = 1.0;
            // Kirilan tuglayi her saniye degistiriyoruz ki Ralph hep
            // ayni yere vurmasin.
            $kirik = ((int) $kalan) % 9;
        } else {
            $vurusFaz = 0.15;
        }
        // ---------- RENKLER ----------
        $acik  = $r($T['siyan']);
        $govde = $r($T['mor'], 40);   // blok kenarlarindaki koyu ton
        $vurgu = $r($T['amber']);
        // Son 10 saniyede rakamlar pembeye doner - aciliyet hissi.
        if ($kalan <= 10) {
            $acik = $r($T['magenta']);
        }
        // ---------- RAKAMLARI YERLESTIR ----------
        // Rakamlar soldan basliyor; Ralph icin sagda yer ayrilmisti,
        // bu yuzden ikisi hicbir zaman cakismaz.
        $x  = $eSol + 10;
        $ry = $RAKAM_UST;
        foreach ($vals as $g => $deger) {
            $n  = $BASAMAKLAR[$g];
            $gy = $ry;
            for ($d = 0; $d < $n; $d++) {
                $hx = $x + $d * ($HANE_G + $HANE_BOS);
                rakamCiz($im, $deger[$d], $hx, $gy, $PIX, $acik, $govde);
            }
            // Etiket
            $grupMerkez = $x + $grupGen[$g] / 2;
            yaz($im, $ETIKETLER[$g], 7, $grupMerkez, $ETIKET_Y, $r($T['baslik']), $FONT, $ttf, 2);
            $x += $grupGen[$g];
            // Ayirac
            if ($g < $ADET - 1) {
                // Yanip sonme: vurus aninda parlak
                $aRenk = ($adim === 0) ? $vurgu : $r($T['amber'], 90);
                ayiracCiz($im, $x + (int) round(($GRUP_BOS - $PIX) / 2), $gy, $PIX, $aRenk, $govde);
                $x += $GRUP_BOS;
            }
        }
        // ---------- TUGLA SIRASI + RALPH ----------
        $tUst = $eAlt - $TUGLA_YUK + 4;
        tuglaCiz(
            $im, $eSol + 2, $eSag - 2, $tUst, $TUGLA_YUK - 6, $kirik,
            [$r($T['tugla']), $r($T['tuglaKen']), $r($T['amber'], 40)]
        );
        // Ralph rakamlarin sagindaki kendi seridinde, tuglalarin uzerinde durur
        $ralphP = $RALPH_P;
        $ralphX = $eSag - $RALPH_G - 6;
        $ralphY = $tUst - 12 * $ralphP + 2;
        ralphCiz(
            $im, $ralphX, $ralphY, $ralphP, $vurusFaz,
            [
                $r($T['ten']),
                $r($T['tulum']),
                [$r($T['amber'])][0],
                $r($T['mor']),
                $r($T['tuglaKen']),
            ]
        );
        // ---------- CRT TARAMA CIZGILERI ----------
        tarama($im, $eSol, $eUst, $eSag, $eAlt, $r($T['ekran'], 100));
        return $im;
    }
    /** CRT tarama cizgileri - her 3 pikselde bir yatay koyu cizgi. */
    function tarama($im, $sol, $ust, $sag, $alt, $renk)
    {
        for ($y = $ust; $y <= $alt; $y += 3) {
            imageline($im, $sol + 1, $y, $sag - 1, $y, $renk);
        }
    }
    // ==================================================================
    // GIF BIRLESTIRICI  —  isin can alici noktasi
    //
    // PHP'nin GD eklentisi animasyonlu GIF uretemez; imagegif() her
    // seferinde tek kareli bir GIF verir. Bu yuzden animasyonu elle
    // kuruyoruz:
    //
    //   1) Her kareyi ayri ayri cizip GD'ye tek kareli GIF yaptiriyoruz.
    //   2) O GIF'lerin icini acip sikistirilmis goruntu verisini ve
    //      renk paletini cekiyoruz (asagidaki gifCoz fonksiyonu).
    //   3) GIF89a dosya yapisini kendimiz yazip kareleri arka arkaya
    //      diziyoruz (en alttaki "GIF'I KUR" bolumu).
    //
    // Goruntu verisini yeniden sikistirmiyoruz; GD'nin sikistirdigi
    // hazir bloklari oldugu gibi tasiyoruz. Bu yuzden hizli calisir.
    // ==================================================================
    /**
     * Tek kareli bir GIF'in icinden renk paletini ve sikistirilmis
     * goruntu verisini cikarir. GIF dosyasi bloklardan olusur; basindan
     * baslayip blok blok ilerliyor, isimize yarayan yere gelince duruyoruz.
     */
    function gifCoz($bin)
    {
        $packed = ord($bin[10]);
        $p      = 13;
        $gct    = '';
        $gctBit = 0;
        if ($packed & 0x80) {
            $gctBit = $packed & 0x07;
            $len    = 3 * (1 << ($gctBit + 1));
            $gct    = substr($bin, $p, $len);
            $p     += $len;
        }
        $son  = strlen($bin);
        $seffafIdx = -1;
        while ($p < $son) {
            $b = ord($bin[$p]);
            if ($b === 0x21) {                       // uzanti blogu
                // Graphic Control Extension ise seffaf renk indeksini al
                if ($p + 1 < $son && ord($bin[$p + 1]) === 0xF9) {
                    $bayrak = ord($bin[$p + 3]);
                    if ($bayrak & 0x01) { $seffafIdx = ord($bin[$p + 6]); }
                }
                $p += 2;
                while ($p < $son && ($sz = ord($bin[$p])) !== 0) { $p += 1 + $sz; }
                $p++;
            } elseif ($b === 0x2C) {                 // goruntu tanimlayici
                $ipacked = ord($bin[$p + 9]);
                $p += 10;
                $pal = $gct;
                $bit = $gctBit;
                if ($ipacked & 0x80) {
                    $bit  = $ipacked & 0x07;
                    $len  = 3 * (1 << ($bit + 1));
                    $pal  = substr($bin, $p, $len);
                    $p   += $len;
                }
                $lzwMin = $bin[$p];
                $p++;
                $veri = '';
                while ($p < $son && ($sz = ord($bin[$p])) !== 0) {
                    $veri .= substr($bin, $p, 1 + $sz);
                    $p    += 1 + $sz;
                }
                $veri .= "\x00";
                return [
                    'pal'    => $pal,
                    'bit'    => $bit,
                    'min'    => $lzwMin,
                    'veri'   => $veri,
                    'seffaf' => $seffafIdx,
                ];
            } else {
                break;
            }
        }
        return null;
    }
    /**
     * Cizilmis bir resmi GIF verisine cevirir.
     *
     * $renkSayisi renk sayisini kisitlar (32, 64 gibi). Az renk =
     * kucuk dosya. Tasarimda zaten az renk kullandigimiz icin gozle
     * gorulur bir kayip olmuyor.
     *
     * Sira onemli: once renkler kisitlaniyor, SONRA saydam renk
     * isaretleniyor. Tersi yapilirsa saydamlik kayboluyor.
     */
    function gifBin($im, $renkSayisi = 0)
    {
        global $tema;
        if ($renkSayisi > 0) {
            imagetruecolortopalette($im, false, $renkSayisi);
        }
        if ($tema === 'seffaf') {
            // Isaret rengini palette buluyoruz. Renk kisitlama sirasinda
            // ton biraz kaymis olabilecegi icin en yakin esi ariyoruz.
            $idx = imagecolorclosest($im, SEFFAF_R, SEFFAF_G, SEFFAF_B);
            if ($idx >= 0) {
                imagecolortransparent($im, $idx);
            }
        }
        imagesavealpha($im, true);
        ob_start();
        imagegif($im);
        return ob_get_clean();
    }
    // ==================================================================
    // KARELERI URET
    //
    // Dosya boyutunu kucuk tutan numara burada. GIF'te her kareyi tam
    // boy gondermek zorunda degilsiniz; sadece degisen bolgeyi gonderip
    // "geri kalani oldugu gibi birak" diyebilirsiniz.
    //
    // Sayacta bir saniyede degisen sey cok az: bir iki rakam, yanip
    // sonen iki nokta ve Ralph. Ekranin geri kalani ayni kaliyor. Bu
    // yuzden her karede yalnizca degisen kucuk dikdortgenleri yaziyoruz.
    // Fark: 210 KB yerine 75 KB.
    // ==================================================================
    /**
     * Iki kareyi karsilastirip degisen bolgeleri dikdortgen listesi
     * olarak dondurur.
     *
     * Neden tek dikdortgen degil de liste? Cunku rakamlar solda, Ralph
     * sagda duruyor. Ikisini tek bir dikdortgene sigdirsaydik aradaki
     * kocaman degismemis alan da her karede bosuna gonderilirdi.
     * Onun yerine degisen sutunlari gruplayip her gruba kendi
     * dikdortgenini veriyoruz - GIF ayni karede birden fazla parcayi
     * zaten destekliyor.
     *
     * @return array  [[sol, ust, genislik, yukseklik], ...]
     *                Hicbir sey degismemisse bos dizi doner.
     */
    function farkKutulari($a, $b, $w, $h)
    {
        // Once degisen sutunlari ve satirlari isaretle
        $sutun = array_fill(0, $w, false);
        $ust = $h; $alt = -1;
        for ($y = 0; $y < $h; $y++) {
            $satirDegisti = false;
            for ($x = 0; $x < $w; $x++) {
                if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
                    $sutun[$x]    = true;
                    $satirDegisti = true;
                }
            }
            if ($satirDegisti) {
                if ($y < $ust) { $ust = $y; }
                $alt = $y;
            }
        }
        if ($alt < 0) { return []; }
        // Bitisik degisen sutunlari gruplara ayir.
        // Aralarinda $ESIK'ten az bos sutun varsa ayni gruba katilir - cok sayida
        // minik blok, palet yuku yuzunden tek bloktan pahali olurdu.
        $ESIK   = 24;
        $gruplar = [];
        $bas    = -1;
        $bosluk = 0;
        for ($x = 0; $x < $w; $x++) {
            if ($sutun[$x]) {
                if ($bas < 0) { $bas = $x; }
                $son    = $x;
                $bosluk = 0;
            } elseif ($bas >= 0) {
                $bosluk++;
                if ($bosluk > $ESIK) {
                    $gruplar[] = [$bas, $son];
                    $bas = -1;
                }
            }
        }
        if ($bas >= 0) { $gruplar[] = [$bas, $son]; }
        // Her grup icin dikey sinirlari daralt
        $kutular = [];
        foreach ($gruplar as $g) {
            list($gs, $gson) = $g;
            $gUst = $h; $gAlt = -1;
            for ($y = $ust; $y <= $alt; $y++) {
                for ($x = $gs; $x <= $gson; $x++) {
                    if (imagecolorat($a, $x, $y) !== imagecolorat($b, $x, $y)) {
                        if ($y < $gUst) { $gUst = $y; }
                        $gAlt = $y;
                        break;
                    }
                }
            }
            if ($gAlt < 0) { continue; }
            $kutular[] = [$gs, $gUst, $gson - $gs + 1, $gAlt - $gUst + 1];
        }
        return $kutular;
    }
    // Uretilen parcalar burada birikiyor. Her eleman:
    //   [sol, ust, goruntuVerisi, kareninSonuMu]
    $kareler     = [];
    $oncekiIm    = null;      // karsilastirma icin bir onceki kare
    $oncekiBitti = null;
    // Saniye saniye ilerleyip her an icin bir kare ciziyoruz.
    for ($i = 0; $i < $KARE_SAYISI; $i++) {
        $kalan = $hedefTs - ($simdi + $i);
        $bitti = ($kalan <= 0);
        // Sure dolduktan sonra ekran hic degismiyor; tek bir
        // "OYUN BITTI" karesi yeter, gerisini uretmiyoruz.
        if ($bitti && $oncekiBitti) { continue; }
        $im = kareCiz($kalan, $i);
        if ($oncekiIm === null) {
            // Ilk kare tam boy gonderilir - uzerine eklenecek bir sey
            // olmadigi icin zemin gorevi gorur.
            //
            // Kucuk bir tuzak: gifBin() resmin renklerini yerinde
            // degistirdigi icin, karsilastirmada kullanacagimiz temiz
            // kopyayi onceden almamiz gerekiyor. Yoksa bir sonraki
            // kareyle karsilastirdigimizda her piksel farkli cikardi.
            $kopya = imagecreatetruecolor($W, $H);
            imagealphablending($kopya, false);
            imagesavealpha($kopya, true);
            imagecopy($kopya, $im, 0, 0, 0, 0, $W, $H);
            $kareler[] = [0, 0, gifBin($im, 128), true];
            imagedestroy($im);
            $im = $kopya;
        } else {
            $kutular = farkKutulari($oncekiIm, $im, $W, $H);
            if (!$kutular) {
                // Hicbir sey degismedi - kareyi atla
                imagedestroy($im);
                continue;
            }
            $sonIdx = count($kutular) - 1;
            foreach ($kutular as $ki => $kutu) {
                list($sol, $ust, $gen, $yuk) = $kutu;
                $kirp = imagecreatetruecolor($gen, $yuk);
                imagealphablending($kirp, false);
                imagesavealpha($kirp, true);
                imagecopy($kirp, $im, 0, 0, $sol, $ust, $gen, $yuk);
                // Yalnizca son blok gecikme tasir; oncekiler ayni anda cizilir.
                $kareler[] = [$sol, $ust, gifBin($kirp, 32), $ki === $sonIdx];
                imagedestroy($kirp);
            }
        }
        if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
        $oncekiIm    = $im;
        $oncekiBitti = $bitti;
    }
    if ($oncekiIm !== null) { imagedestroy($oncekiIm); }
    // ==================================================================
    // GIF'I KUR
    //
    // Elimizde kare kare goruntu parcalari var; simdi bunlari GIF89a
    // dosya yapisina gore arka arkaya diziyoruz. Sira sabittir:
    //   basik bilgi -> renk paleti -> dongu ayari -> kareler -> bitis
    // ==================================================================
    $ilk = gifCoz($kareler[0][2]);
    // --- Dosya basligi: imza, olculer, renk paleti ---
    $out  = "GIF89a";
    $out .= pack('v', $W) . pack('v', $H);
    $out .= chr(0x80 | 0x70 | $ilk['bit']) . "\x00\x00";
    $out .= $ilk['pal'];
    // --- Dongu ayari ---
    // Tarayicilara animasyonun kac kez oynayacagini soyleyen blok.
    // ($DONGU 1 ise bir kez oynar, 0 ise sonsuz doner.)
    $out .= "\x21\xFF\x0B" . "NETSCAPE2.0" . "\x03\x01" . pack('v', $DONGU) . "\x00";
    // --- Kareler ---
    foreach ($kareler as $idx => $k) {
        list($sol, $ust, $bin, $gecikmeVer) = $k;
        $f = gifCoz($bin);
        if ($f === null) { continue; }
        // Her parcanin onune bir kontrol blogu koyuyoruz. Burada iki
        // sey soyluyoruz: bu parca ne kadar ekranda kalacak ve
        // silinmeden yerinde birakilacak (silinseydi altindaki kare
        // kaybolur, ekran yanip sonerdi).
        //
        // Bir kareyi birden fazla parcaya bolmustuk. O parcalarin
        // yalnizca sonuncusuna bekleme suresi veriyoruz; oncekiler
        // 0 sureyle ard arda cizildigi icin izleyici tek kare gorur.
        $bayrak = (1 << 2);
        $seffafIdx = 0;
        if ($f['seffaf'] >= 0) {
            $bayrak |= 0x01;
            $seffafIdx = $f['seffaf'];
        }
        $out .= "\x21\xF9\x04" . chr($bayrak)
              . pack('v', $gecikmeVer ? $KARE_SURESI : 0)
              . chr($seffafIdx) . "\x00";
        // Parcanin ekranda nereye oturacagini ve olculerini yaziyoruz.
        // Tam boy olmadigi icin konum bilgisi onemli: tarayici bu
        // parcayi onceki karenin uzerine, tam bu noktaya cizecek.
        $gen = ord($bin[6]) | (ord($bin[7]) << 8);
        $yuk = ord($bin[8]) | (ord($bin[9]) << 8);
        $out .= "\x2C" . pack('v', $sol) . pack('v', $ust) . pack('v', $gen) . pack('v', $yuk);
        // Parcanin kendi renk paleti ve sikistirilmis goruntu verisi.
        $out .= chr(0x80 | $f['bit']) . $f['pal'];
        $out .= $f['min'] . $f['veri'];
    }
    // --- Dosya sonu isareti ---
    $out .= "\x3B";
    // ==================================================================
    // ONBELLEGE YAZ
    // ==================================================================
    if ($onbellekDosya !== null) {
        // Once gecici bir dosyaya yazip sonra asil adina tasiyoruz.
        // Boylece ayni anda gelen baska bir istek, yarim yazilmis
        // dosyayi okuyup bozuk resim gostermez.
        $gecici = $onbellekDosya . '.' . getmypid() . '.tmp';
        if (@file_put_contents($gecici, $out) === strlen($out)) {
            if (!@rename($gecici, $onbellekDosya)) { @unlink($gecici); }
        } else {
            // Yazamadiysak (izin yok, disk dolu) sorun degil; resim
            // yine gonderilir, sadece onbellekten yararlanilmaz.
            @unlink($gecici);
        }
        // Ayarlari her degistirdiginizde yeni bir dosya adi olusur ve
        // eskisi oldugu yerde kalir. Klasor sismesin diye ara sira
        // (yaklasik her 50 istekte bir) 1 saatten eski dosyalari
        // siliyoruz. Her istekte yapmak gereksiz yere disk yorardi.
        if (mt_rand(1, 50) === 1) {
            foreach ((array) @glob($ONBELLEK_DIZ . '/*.gif') as $eski) {
                if (time() - (int) @filemtime($eski) > 3600) { @unlink($eski); }
            }
        }
    }
    // ==================================================================
    // CIKTI
    // ==================================================================
    gifGonder($out);
    teşekkür ederim