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);