start of transfer

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2024-01-05 01:06:52 +07:00
commit e6150b789a
26 changed files with 1739 additions and 0 deletions

4
.gitignore vendored Executable file
View File

@ -0,0 +1,4 @@
!.gitignore
composer.phar
composer.lock
vendor

11
LICENSE Executable file
View File

@ -0,0 +1,11 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

1
README.md Executable file
View File

@ -0,0 +1 @@
# Site for my father

48
composer.json Executable file
View File

@ -0,0 +1,48 @@
{
"name": "mirzaev/gavno",
"description": "",
"readme": "README.md",
"keywords": [
"landing",
"engeneering",
"site"
],
"type": "site",
"homepage": "https://git.mirzaev.sexy/mirzaev/gavno",
"license": "WTFPL",
"authors": [
{
"name": "Arsen Mirzaev Tatyano-Muradovich",
"email": "arsen@mirzaev.sexy",
"homepage": "https://mirzaev.sexy",
"role": "Programmer"
}
],
"support": {
"docs": "https://git.mirzaev.sexy/mirzaev/gavno/manual",
"issues": "https://git.mirzaev.sexy/mirzaev/gavno/issues"
},
"require": {
"php": "~8.3",
"ext-sodium": "~8.3",
"mirzaev/minimal": "^2.2.0",
"mirzaev/arangodb": "^1.0.0",
"triagens/arangodb": "~3.9.x-dev",
"twig/twig": "^3.4",
"guzzlehttp/guzzle": "^7.5",
"ipinfo/ipinfo": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "~9.5"
},
"autoload": {
"psr-4": {
"mirzaev\\gavno\\": "mirzaev/gavno/system"
}
},
"autoload-dev": {
"psr-4": {
"mirzaev\\gavno\\tests\\": "mirzaev/gavno/tests"
}
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace mirzaev\gavno\controllers;
// Файлы проекта
use mirzaev\gavno\views\manager;
use mirzaev\gavno\models\core as models;
use mirzaev\gavno\models\account_model as account;
use mirzaev\gavno\models\session_model as session;
// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;
// Фреймворк PHP
use mirzaev\minimal\controller;
// Фреймворк ВКонтакте
use mirzaev\vk\core as vk;
use mirzaev\vk\robots\user as robot;
/**
* Ядро контроллеров
*
* @package mirzaev\gavno\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class core extends controller
{
/**
* Переменные окружения
*/
protected robot $vk;
/**
* Переменные окружения
*/
protected array $variables = [];
/**
* Конструктор
*
* @return void
*/
public function __construct() {
parent::__construct();
// Инициализация ядра моделей (соединение с базой данных...)
new models();
// Инициализация журнала ошибок
$this->variables['errors'] = [
'vk' => []
];
// Инициализация препроцессора представления
$this->view = new manager;
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace mirzaev\gavno\controllers;
// Файлы проекта
use mirzaev\gavno\controllers\core;
/**
* Контроллер ошибок
*
* @package mirzaev\gavno\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class error_controller extends core
{
/**
* Страница с ошибкой
*
* @param array $parameters
*/
public function index(array $parameters = []): ?string
{
// Запись текста ошибки в переменную окружения
$this->variables['text'] = $parameters['text'] ?? null;
if (isset($parameters['code'])) {
// Получен код ошибки
// Запись кода ошибки в переменную окружения
$this->variables['code'] = $parameters['code'];
// Запись кода ответа
http_response_code($parameters['code']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . 'index.html', $this->variables);
}
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . ($parameters['code'] ?? 'index') . '.html', $this->variables);
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace mirzaev\gavno\controllers;
// Файлы проекта
use mirzaev\gavno\controllers\core,
mirzaev\gavno\models\views;
/**
* Контроллер основной страницы
*
* @package mirzaev\gavno\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class index_controller extends core
{
/**
* Главная страница
*
* @param array $parameters Параметры запроса
*/
public function index(array $parameters = []): ?string
{
// Инициализация шутника
$this->variables['troller'] = [
'instasamka' => rand(1, 11),
'southern' => rand(1, 3),
'vk' => (bool) rand(0, 1),
'whatsapp' => (bool) rand(0, 1),
'iphone' => (bool) rand(0, 1),
];
// Запись просмотра
views::increase();
// Инициализация счётчика просмотров
$this->variables['views'] = [
'day' => views::day(),
'week' => views::week(),
'month' => views::month(),
'all' => views::all(),
'last' => views::last(10)
];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', $this->variables);
}
}

View File

@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
namespace mirzaev\gavno\models;
// Фреймворк PHP
use mirzaev\minimal\model;
// Фреймворк ArangoDB
use mirzaev\arangodb\connection as arangodb;
use exception;
/**
* Ядро моделей
*
* @package mirzaev\gavno\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class core extends model
{
/**
* Соединение с базой данных ArangoDB
*/
protected static arangodb $arangodb;
/**
* Путь до файла с настройками подключения к базе данных ArangoDB
*/
final public const ARANGODB = '../settings/arangodb.php';
/**
* Конструктор
*
* @param bool $initialize Инициализировать контроллер?
* @param ?arangodb $arangodb Инстанция соединения с базой данных ArangoDB
*/
public function __construct(bool $initialize = true, ?arangodb $arangodb = null)
{
if ($initialize) {
// Запрошена инициализация
if (isset($arangodb)) {
// Получена инстанция соединения с базой данных
// Запись и инициализация соединения с базой данных
$this->__set('arangodb', $arangodb);
} else {
// Не получена инстанция соединения с базой данных
// Инициализация соединения с базой данных по умолчанию
$this->__get('arangodb');
}
}
}
/**
* Записать свойство
*
* @param string $name Название
* @param mixed $value Значение
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'arangodb' => (function () use ($value) {
if ($this->__isset('arangodb')) {
// Свойство уже было инициализировано
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать соединение с базой данных ($this->arangodb)', 500);
} else {
// Свойство ещё не было инициализировано
if ($value instanceof arangodb) {
// Передано подходящее значение
// Запись свойства (успех)
self::$arangodb = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Соединение с базой данных ($this->arangodb) должен быть инстанцией mirzaev\arangodb\connection', 500);
}
}
})(),
default => parent::__set($name, $value)
};
}
/**
* Прочитать свойство
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
'arangodb' => (function () {
if (!$this->__isset('db')) {
// Свойство не инициализировано
// Инициализация значения по умолчанию исходя из настроек
$this->__set('arangodb', new arangodb(require static::ARANGODB));
}
return self::$arangodb;
})(),
default => parent::__get($name)
};
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*/
public function __isset(string $name): bool
{
return match ($name) {
default => parent::__isset($name)
};
}
/**
* Удалить свойство
*
* @param string $name Название
*/
public function __unset(string $name): void
{
match ($name) {
default => parent::__isset($name)
};
}
/**
* Статический вызов
*
* @param string $name Название
* @param array $arguments Параметры
*/
public static function __callStatic(string $name, array $arguments): mixed
{
match ($name) {
default => throw new exception("Не найдено свойство или функция: $name", 500)
};
}
}

View File

@ -0,0 +1,224 @@
<?php
declare(strict_types=1);
namespace mirzaev\gavno\models;
// Фреймворк ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Фреймворк для сайта ipinfo.io
use ipinfo\ipinfo\IPinfo;
// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;
// Встроенные библиотеки
use exception;
/**
* Счётчик просмотров
*
* @package mirzaev\gavno\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class views extends core
{
/**
* Коллекция
*/
final public const COLLECTION = 'views';
public static function increase(array &$errors = []): ?bool
{
try {
if (collection::init(static::$arangodb->session, self::COLLECTION))
if ($_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return null;
else if (document::write(static::$arangodb->session, self::COLLECTION, [
'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
'x-forwarded-for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null,
'referer' => $_SERVER['HTTP_REFERER'] ?? null,
'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? null
] + (array) (new IPinfo(require '../settings/ipinfo.php'))->getDetails($_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR']))) return true;
else throw new exception('Не удалось создать аккаунт');
else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
public static function day(array &$errors = []): ?int
{
try {
if (collection::init(static::$arangodb->session, self::COLLECTION))
return collection::search(static::$arangodb->session, sprintf(
<<<AQL
return COUNT_DISTINCT(
FOR d IN %s
FILTER d.created >= %d
RETURN d['x-forwarded-for']
)
AQL,
views::COLLECTION,
time() - 86400
));
else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
public static function week(array &$errors = []): ?int
{
try {
if (collection::init(static::$arangodb->session, self::COLLECTION))
return collection::search(static::$arangodb->session, sprintf(
<<<AQL
return COUNT_DISTINCT(
FOR d IN %s
FILTER d.created >= %d
RETURN d['x-forwarded-for']
)
AQL,
views::COLLECTION,
time() - 604800
));
else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
public static function month(array &$errors = []): ?int
{
try {
if (collection::init(static::$arangodb->session, self::COLLECTION))
return collection::search(static::$arangodb->session, sprintf(
<<<AQL
return COUNT_DISTINCT(
FOR d IN %s
FILTER d.created >= %d
RETURN d['x-forwarded-for']
)
AQL,
views::COLLECTION,
time() - 2592000
));
else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
public static function all(array &$errors = []): ?int
{
try {
if (collection::init(static::$arangodb->session, self::COLLECTION))
return collection::search(static::$arangodb->session, sprintf(
<<<AQL
return COUNT_DISTINCT(
FOR d IN %s
RETURN d['x-forwarded-for']
)
AQL,
views::COLLECTION
));
else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
public static function last(int $amount = 10, array &$errors = []): ?array
{
try {
if (collection::init(static::$arangodb->session, self::COLLECTION)) {
// Инициализирована коллекция
// Поиск последних просмотров
$response = @collection::search(static::$arangodb->session, sprintf(
<<<AQL
FOR ip in (
FOR d IN views
SORT d.created DESC
RETURN DISTINCT d['x-forwarded-for'] == null ? d.ip : d['x-forwarded-for']
)
LIMIT 10
RETURN (
FOR d IN views
SORT d.created DESC
FILTER d['x-forwarded-for'] == ip || d.ip == ip
LIMIT 1
RETURN d
)
AQL,
views::COLLECTION,
$amount
));
// Инициализация буфера обработанных последних просмотров
$buffer = [];
// Универсализация
if ($response instanceof _document) $response = [$response];
// Обработка последних просмотров
foreach ($response ?? [] as $view) $buffer[] = $view->getAll()[0];
return $buffer;
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
}

View File

@ -0,0 +1,326 @@
@font-face {
font-family: comissioner;
src: url("/fonts/commissioner.ttf") format("ttf");
font-weight: normal;
font-style: normal;
}
:root {
--button-light-red-active: #eee4e4;
--button-light-red-hover: #ddcbcb;
--button-light-red: #eadada;
--background-light: #fff;
--background: #f00;
--background-dark: #000;
--text: #020202;
--text-light: #fafafa;
--text-hover: #fff;
--text-active: #d0d0d0;
--red-light-1: #dc4343;
--red-light: #bf3737;
--red: #a43333;
--red-dark: #8d2a2a;
--grey-light: #c0c0c0;
--grey: #858585;
--grey-dark: #565656;
}
* {
text-decoration: none;
outline: none;
border: none;
font-family: commissioner, Roboto, sans-serif;
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
body {
margin: 0;
background-color: #1a1a1a;
color: #ffffffdd;
position: relative;
overflow: hidden;
font-family: "Inter", sans-serif;
}
.card {
position: absolute;
left: 0;
top: 0;
background-position: center;
background-size: cover;
box-shadow: 6px 6px 10px 2px rgba(0, 0, 0, 0.6);
}
#btn {
position: absolute;
top: 690px;
left: 16px;
z-index: 99;
}
.card-content {
position: absolute;
left: 0;
top: 0;
color: #ffffffdd;
padding-left: 16px;
}
.content-place {
margin-top: 6px;
font-size: 13px;
font-weight: 500;
}
.content-place {
font-weight: 500;
}
.content-title-1,
.content-title-2 {
font-weight: 600;
font-size: 20px;
font-family: "Oswald", sans-serif;
}
.content-start {
width: 30px;
height: 5px;
border-radius: 99px;
background-color: #ffffffdd;
}
.details {
z-index: 22;
position: absolute;
top: 240px;
left: 60px;
}
.details .place-box {
height: 46px;
overflow: hidden;
}
.details .place-box .text {
padding-top: 16px;
font-size: 20px;
}
.details .place-box .text:before {
top: 0;
left: 0;
position: absolute;
content: "";
width: 30px;
height: 4px;
border-radius: 99px;
background-color: white;
}
.details .title-1,
.details .title-2 {
font-weight: 600;
font-size: 72px;
font-family: "Oswald", sans-serif;
}
.details .title-box-1,
.details .title-box-2 {
margin-top: 2px;
height: 100px;
overflow: hidden;
}
.details > .desc {
margin-top: 16px;
width: 500px;
}
.details > .cta {
width: 500px;
margin-top: 24px;
display: flex;
align-items: center;
}
.details > .cta > .bookmark {
border: none;
background-color: #ecad29;
width: 36px;
height: 36px;
border-radius: 99px;
color: white;
display: grid;
place-items: center;
}
.details > .cta > .bookmark svg {
width: 20px;
height: 20px;
}
.details > .cta > .discover {
border: 1px solid #ffffff;
background-color: transparent;
height: 36px;
border-radius: 99px;
color: #ffffff;
padding: 4px 24px;
font-size: 12px;
margin-left: 16px;
text-transform: uppercase;
}
nav {
position: fixed;
left: 0;
top: 0;
right: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 36px;
font-weight: 500;
}
nav svg {
width: 20px;
height: 20px;
}
nav .svg-container {
width: 20px;
height: 20px;
}
nav > div {
display: inline-flex;
align-items: center;
text-transform: uppercase;
font-size: 14px;
}
nav > div:first-child {
gap: 10px;
}
nav > div:last-child {
gap: 24px;
}
nav > div:last-child > .active {
position: relative;
}
nav > div:last-child > .active:after {
bottom: -8px;
left: 0;
right: 0;
position: absolute;
content: "";
height: 3px;
border-radius: 99px;
background-color: #ecad29;
}
.indicator {
position: fixed;
left: 0;
right: 0;
top: 0;
height: 5px;
z-index: 60;
background-color: #ecad29;
}
.pagination {
position: absolute;
left: 0px;
top: 0px;
display: inline-flex;
}
.pagination > .arrow {
z-index: 60;
width: 50px;
height: 50px;
border-radius: 999px;
border: 2px solid #ffffff55;
display: grid;
place-items: center;
}
.pagination > .arrow:nth-child(2) {
margin-left: 20px;
}
.pagination > .arrow svg {
width: 24px;
height: 24px;
stroke-width: 2;
color: #ffffff99;
}
.pagination .progress-sub-container {
margin-left: 24px;
z-index: 60;
width: 500px;
height: 50px;
display: flex;
align-items: center;
}
.pagination .progress-sub-container .progress-sub-background {
width: 500px;
height: 3px;
background-color: #ffffff33;
}
.pagination
.progress-sub-container
.progress-sub-background
.progress-sub-foreground {
height: 3px;
background-color: #ecad29;
}
.pagination .slide-numbers {
width: 50px;
height: 50px;
overflow: hidden;
z-index: 60;
position: relative;
}
.pagination .slide-numbers .item {
width: 50px;
height: 50px;
position: absolute;
color: white;
top: 0;
left: 0;
display: grid;
place-items: center;
font-size: 32px;
font-weight: bold;
}
.cover {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-color: #fff;
z-index: 100;
}

Binary file not shown.

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace mirzaev\gavno;
use mirzaev\minimal\core;
use mirzaev\minimal\router;
ini_set('error_reporting', E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
define('INDEX', __DIR__);
// Автозагрузка
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
// Инициализация маршрутизатора
$router = new router;
// Запись маршрутов
$router->write('/', 'index', 'index');
// Инициализация ядра
$core = new core(namespace: __NAMESPACE__, router: $router);
// Обработка запроса
echo $core->start();

View File

@ -0,0 +1,379 @@
const data = [
{
place: "Switzerland Alps",
title: "SAINT",
title2: "ANTONIEN",
description:
"Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. It's a hidden gem for backcountry skiing in winter and boasts lush trails for hiking and mountain biking during the warmer months.",
image: "https://assets.codepen.io/3685267/timed-cards-1.jpg",
},
{
place: "Japan Alps",
title: "NANGANO",
title2: "PREFECTURE",
description:
"Nagano Prefecture, set within the majestic Japan Alps, is a cultural treasure trove with its historic shrines and temples, particularly the famous Zenkō-ji. The region is also a hotspot for skiing and snowboarding, offering some of the country's best powder.",
image: "https://assets.codepen.io/3685267/timed-cards-2.jpg",
},
{
place: "Sahara Desert - Morocco",
title: "MARRAKECH",
title2: "MEROUGA",
description:
"The journey from the vibrant souks and palaces of Marrakech to the tranquil, starlit sands of Merzouga showcases the diverse splendor of Morocco. Camel treks and desert camps offer an unforgettable immersion into the nomadic way of life.",
image: "https://assets.codepen.io/3685267/timed-cards-3.jpg",
},
{
place: "Sierra Nevada - USA",
title: "YOSEMITE",
title2: "NATIONAL PARAK",
description:
"Yosemite National Park is a showcase of the American wilderness, revered for its towering granite monoliths, ancient giant sequoias, and thundering waterfalls. The park offers year-round recreational activities, from rock climbing to serene valley walks.",
image: "https://assets.codepen.io/3685267/timed-cards-4.jpg",
},
{
place: "Tarifa - Spain",
title: "LOS LANCES",
title2: "BEACH",
description:
"Los Lances Beach in Tarifa is a coastal paradise known for its consistent winds, making it a world-renowned spot for kitesurfing and windsurfing. The beach's long, sandy shores provide ample space for relaxation and sunbathing, with a vibrant atmosphere of beach bars and cafes.",
image: "https://assets.codepen.io/3685267/timed-cards-5.jpg",
},
{
place: "Cappadocia - Turkey",
title: "Göreme",
title2: "Valley",
description:
"Göreme Valley in Cappadocia is a historical marvel set against a unique geological backdrop, where centuries of wind and water have sculpted the landscape into whimsical formations. The valley is also famous for its open-air museums, underground cities, and the enchanting experience of hot air ballooning.",
image: "https://assets.codepen.io/3685267/timed-cards-6.jpg",
},
];
const _ = (id) => document.getElementById(id);
const cards = data
.map(
(i, index) =>
`<div class="card" id="card${index}" style="background-image:url(${i.image})" ></div>`,
)
.join("");
const cardContents = data
.map(
(i, index) =>
`<div class="card-content" id="card-content-${index}">
<div class="content-start"></div>
<div class="content-place">${i.place}</div>
<div class="content-title-1">${i.title}</div>
<div class="content-title-2">${i.title2}</div>
</div>`,
)
.join("");
const sildeNumbers = data
.map(
(_, index) =>
`<div class="item" id="slide-item-${index}" >${index + 1}</div>`,
)
.join("");
_("demo").innerHTML = cards + cardContents;
_("slide-numbers").innerHTML = sildeNumbers;
const range = (n) =>
Array(n)
.fill(0)
.map((i, j) => i + j);
const set = gsap.set;
function getCard(index) {
return `#card${index}`;
}
function getCardContent(index) {
return `#card-content-${index}`;
}
function getSliderItem(index) {
return `#slide-item-${index}`;
}
function animate(target, duration, properties) {
return new Promise((resolve) => {
gsap.to(target, {
...properties,
duration: duration,
onComplete: resolve,
});
});
}
let order = [0, 1, 2, 3, 4, 5];
let detailsEven = true;
let offsetTop = 200;
let offsetLeft = 700;
let cardWidth = 200;
let cardHeight = 300;
let gap = 40;
let numberSize = 50;
const ease = "sine.inOut";
function init() {
const [active, ...rest] = order;
const detailsActive = detailsEven ? "#details-even" : "#details-odd";
const detailsInactive = detailsEven ? "#details-odd" : "#details-even";
const { innerHeight: height, innerWidth: width } = window;
offsetTop = height - 430;
offsetLeft = width - 830;
gsap.set("#pagination", {
top: offsetTop + 330,
left: offsetLeft,
y: 200,
opacity: 0,
zIndex: 60,
});
gsap.set("nav", { y: -200, opacity: 0 });
gsap.set(getCard(active), {
x: 0,
y: 0,
width: window.innerWidth,
height: window.innerHeight,
});
gsap.set(getCardContent(active), { x: 0, y: 0, opacity: 0 });
gsap.set(detailsActive, { opacity: 0, zIndex: 22, x: -200 });
gsap.set(detailsInactive, { opacity: 0, zIndex: 12 });
gsap.set(`${detailsInactive} .text`, { y: 100 });
gsap.set(`${detailsInactive} .title-1`, { y: 100 });
gsap.set(`${detailsInactive} .title-2`, { y: 100 });
gsap.set(`${detailsInactive} .desc`, { y: 50 });
gsap.set(`${detailsInactive} .cta`, { y: 60 });
gsap.set(".progress-sub-foreground", {
width: 500 * (1 / order.length) * (active + 1),
});
rest.forEach((i, index) => {
gsap.set(getCard(i), {
x: offsetLeft + 400 + index * (cardWidth + gap),
y: offsetTop,
width: cardWidth,
height: cardHeight,
zIndex: 30,
borderRadius: 10,
});
gsap.set(getCardContent(i), {
x: offsetLeft + 400 + index * (cardWidth + gap),
zIndex: 40,
y: offsetTop + cardHeight - 100,
});
gsap.set(getSliderItem(i), { x: (index + 1) * numberSize });
});
gsap.set(".indicator", { x: -window.innerWidth });
const startDelay = 0.6;
gsap.to(".cover", {
x: width + 400,
delay: 0.5,
ease,
onComplete: () => {
setTimeout(() => {
loop();
}, 500);
},
});
rest.forEach((i, index) => {
gsap.to(getCard(i), {
x: offsetLeft + index * (cardWidth + gap),
zIndex: 30,
delay: 0.05 * index,
ease,
delay: startDelay,
});
gsap.to(getCardContent(i), {
x: offsetLeft + index * (cardWidth + gap),
zIndex: 40,
delay: 0.05 * index,
ease,
delay: startDelay,
});
});
gsap.to("#pagination", { y: 0, opacity: 1, ease, delay: startDelay });
gsap.to("nav", { y: 0, opacity: 1, ease, delay: startDelay });
gsap.to(detailsActive, { opacity: 1, x: 0, ease, delay: startDelay });
}
let clicks = 0;
function step() {
return new Promise((resolve) => {
order.push(order.shift());
detailsEven = !detailsEven;
const detailsActive = detailsEven ? "#details-even" : "#details-odd";
const detailsInactive = detailsEven ? "#details-odd" : "#details-even";
document.querySelector(`${detailsActive} .place-box .text`).textContent =
data[order[0]].place;
document.querySelector(`${detailsActive} .title-1`).textContent =
data[order[0]].title;
document.querySelector(`${detailsActive} .title-2`).textContent =
data[order[0]].title2;
document.querySelector(`${detailsActive} .desc`).textContent =
data[order[0]].description;
gsap.set(detailsActive, { zIndex: 22 });
gsap.to(detailsActive, { opacity: 1, delay: 0.4, ease });
gsap.to(`${detailsActive} .text`, {
y: 0,
delay: 0.1,
duration: 0.7,
ease,
});
gsap.to(`${detailsActive} .title-1`, {
y: 0,
delay: 0.15,
duration: 0.7,
ease,
});
gsap.to(`${detailsActive} .title-2`, {
y: 0,
delay: 0.15,
duration: 0.7,
ease,
});
gsap.to(`${detailsActive} .desc`, {
y: 0,
delay: 0.3,
duration: 0.4,
ease,
});
gsap.to(`${detailsActive} .cta`, {
y: 0,
delay: 0.35,
duration: 0.4,
onComplete: resolve,
ease,
});
gsap.set(detailsInactive, { zIndex: 12 });
const [active, ...rest] = order;
const prv = rest[rest.length - 1];
gsap.set(getCard(prv), { zIndex: 10 });
gsap.set(getCard(active), { zIndex: 20 });
gsap.to(getCard(prv), { scale: 1.5, ease });
gsap.to(getCardContent(active), {
y: offsetTop + cardHeight - 10,
opacity: 0,
duration: 0.3,
ease,
});
gsap.to(getSliderItem(active), { x: 0, ease });
gsap.to(getSliderItem(prv), { x: -numberSize, ease });
gsap.to(".progress-sub-foreground", {
width: 500 * (1 / order.length) * (active + 1),
ease,
});
gsap.to(getCard(active), {
x: 0,
y: 0,
ease,
width: window.innerWidth,
height: window.innerHeight,
borderRadius: 0,
onComplete: () => {
const xNew = offsetLeft + (rest.length - 1) * (cardWidth + gap);
gsap.set(getCard(prv), {
x: xNew,
y: offsetTop,
width: cardWidth,
height: cardHeight,
zIndex: 30,
borderRadius: 10,
scale: 1,
});
gsap.set(getCardContent(prv), {
x: xNew,
y: offsetTop + cardHeight - 100,
opacity: 1,
zIndex: 40,
});
gsap.set(getSliderItem(prv), { x: rest.length * numberSize });
gsap.set(detailsInactive, { opacity: 0 });
gsap.set(`${detailsInactive} .text`, { y: 100 });
gsap.set(`${detailsInactive} .title-1`, { y: 100 });
gsap.set(`${detailsInactive} .title-2`, { y: 100 });
gsap.set(`${detailsInactive} .desc`, { y: 50 });
gsap.set(`${detailsInactive} .cta`, { y: 60 });
clicks -= 1;
if (clicks > 0) {
step();
}
},
});
rest.forEach((i, index) => {
if (i !== prv) {
const xNew = offsetLeft + index * (cardWidth + gap);
gsap.set(getCard(i), { zIndex: 30 });
gsap.to(getCard(i), {
x: xNew,
y: offsetTop,
width: cardWidth,
height: cardHeight,
ease,
delay: 0.1 * (index + 1),
});
gsap.to(getCardContent(i), {
x: xNew,
y: offsetTop + cardHeight - 100,
opacity: 1,
zIndex: 40,
ease,
delay: 0.1 * (index + 1),
});
gsap.to(getSliderItem(i), { x: (index + 1) * numberSize, ease });
}
});
});
}
async function loop() {
await animate(".indicator", 2, { x: 0 });
await animate(".indicator", 0.8, { x: window.innerWidth, delay: 0.3 });
set(".indicator", { x: -window.innerWidth });
await step();
loop();
}
async function loadImage(src) {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
async function loadImages() {
const promises = data.map(({ image }) => loadImage(image));
return Promise.all(promises);
}
async function start() {
try {
await loadImages();
init();
} catch (error) {
console.error("One or more images failed to load", error);
}
}
start();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
/*! js-cookie v3.0.1 | MIT */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)e[o]=n[o]}return e}return function t(n,o){function r(t,r,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var c="";for(var u in i)i[u]&&(c+="; "+u,!0!==i[u]&&(c+="="+i[u].split(";")[0]));return document.cookie=t+"="+n.write(r,t)+c}}return Object.create({set:r,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var t=document.cookie?document.cookie.split("; "):[],o={},r=0;r<t.length;r++){var i=t[r].split("="),c=i.slice(1).join("=");try{var u=decodeURIComponent(i[0]);if(o[u]=n.read(c,u),e===u)break}catch(e){}}return e?o[e]:o}},remove:function(t,n){r(t,"",e({},n,{expires:-1}))},withAttributes:function(n){return t(this.converter,e({},this.attributes,n))},withConverter:function(n){return t(e({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(n)}})}({read:function(e){return'"'===e[0]&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}},{path:"/"})}));

View File

@ -0,0 +1,3 @@
*
!.gitignore
!*.sample

View File

@ -0,0 +1,8 @@
<?php
return [
'endpoint' => 'unix:///var/run/arangodb3/arango.sock',
'database' => 'repression',
'name' => 'repression',
'password' => ''
];

View File

@ -0,0 +1,3 @@
<?php
return '';

View File

@ -0,0 +1,19 @@
{% use 'hotline/index.html' with css as hotline_css, body as hotline_body, js as hotline_js, js_init as hotline_js_init %}
{% block css %}
{# {{ block('hotline_css') }} #}
{% endblock %}
{% block body %}
<aside>
{{ block('hotline_body') }}
</aside>
{% endblock %}
{% block js %}
{# {{ block('hotline_js') }} #}
{% endblock %}
{% block js_init %}
{# {{ block('hotline_js_init') }} #}
{% endblock %}

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en" prefix="og: https://ogp.me/ns#">
<head>
{% use 'head.html' with title as head_title, meta as head_meta, css as head_css %}
{% block title %}
{{ block('head_title') }}
{% endblock %}
{% block meta %}
{{ block('head_meta') }}
{% endblock %}
{% block css %}
{{ block('head_css') }}
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
{% block js %}
{% include 'js.html' %}
{% endblock %}
{% block js_init %}
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,2 @@
<footer>
</footer>

View File

@ -0,0 +1,47 @@
{% block title %}
<title>{% if head.title != empty %}{{head.title}}{% else %}test{% endif %}</title>
{% endblock %}
{% block meta %}
<meta charset="utf-8" />
<meta name="viewport" content="width=800px" />
<meta name="HandheldFriendly" content="true" />
<meta name="MobileOptimized" content="width" />
<meta content='yes' name='apple-mobile-web-app-capable' />
<meta name="description"
content="I was kidnapped and recruited by PMC Wagner in Khabarovsk due to anti-government posts on my blog" />
<meta name="keywords" content="MIRZAEV, PMC Wagner, Khabarovsk, Repression" />
<meta name="author" content="MIRZAEV" />
<meta name="revised" content="29-05-2023" />
<meta property="og:title" content="PMC WAGNER KIDNAPPED ME" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2023-06-01" />
<meta property="article:author" content="MIRZAEV" />
<meta property="article:section" content="politics" />
<meta property="article:tag" content="MIRZAEV, PMC Wagner, Khabarovsk, Repression" />
<meta property="og:url" content="https://repression.mirzaev.sexy/" />
<meta property="og:image" content="https://repression.mirzaev.sexy/images/mirzaev.jpg" />
<meta property="og:image" content="https://repression.mirzaev.sexy/images/mirzaev2.png" />
<meta property="og:description"
content="I was kidnapped and recruited by PMC Wagner in Khabarovsk due to anti-government posts on my blog" />
<meta property="og:site_name" content="mirzaev.sexy" />
<meta property="og:video" content="https://repression.mirzaev.sexy/videos/welcome.mp4" />
<meta property="og:audio" content="https://repression.mirzaev.sexy/sounds/putin.mp3" />
<meta property="twitter:card" content="summary">
<meta property="twitter:creator" content="@mirzaev_sexy">
<meta property="twitter:site" content="@mirzaev_sexy">
<meta property="twitter:url" content="https://repression.mirzaev.sexy/">
<meta property="twitter:title" content="PMC WAGNER KIDNAPPED ME">
<meta property="twitter:description"
content="I was kidnapped and recruited by Wagner PMC in Khabarovsk due to anti-government posts on my blog">
<meta property="twitter:image" content="https://repression.mirzaev.sexy/images/mirzaev.jpg">
<link rel="icon" type="image/png" href="/images/favicon/favicon.png">
{% for meta in head.metas %}
<meta {% for name, value in meta.attributes %}{{name}}="{{value}}" {% endfor %} />
{% endfor %}
{% endblock %}
{% block css %}
<link rel="stylesheet" type="text/css" href="/css/main.css" />
<link rel="stylesheet" type="text/css" href="/css/icon_eye.css" />
{% endblock %}

View File

@ -0,0 +1,11 @@
{% block css %}
{% endblock %}
{% block body %}
{% endblock %}
{% block js %}
{% endblock %}
{% block js_init %}
{% endblock %}

View File

@ -0,0 +1,238 @@
{% extends "core.html" %}
{% use "core.html" with css as core_css, body as core_body, js as core_js, js_init as core_js_init %}
{% use "header.html" with css as header_css, body as header_body, js as header_js, js_init as header_js_init %}
{% use 'account/element.html' with css as account_css, body as account_body, js as account_js %}
{% block css %}
{{ block('core_css') }}
{{ block('header_css') }}
{{ block('account_css') }}
{% endblock %}
{% block body %}
{{ block('account_body') }}
{{ block('core_body') }}
<noscript>My site cannot work without JavaScript</noscript>
<!-- {{ block('header_body') }} -->
{% block main %}
<div class="indicator"></div>
<nav>
<div>
<div class="svg-container">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" />
</svg>
</div>
<div>Globe Express</div>
</div>
<div>
<div class="active">Home</div>
<div>Holidays</div>
<div>Destinations</div>
<div>Flights</div>
<div>Offers</div>
<div>Contact</div>
<div class="svg-container">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</svg>
</div>
<div class="svg-container">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z"
clip-rule="evenodd" />
</svg>
</div>
</div>
</nav>
<div id="demo"></div>
<div class="details" id="details-even">
<div class="place-box">
<div class="text">Switzerland Alps</div>
</div>
<div class="title-box-1">
<div class="title-1">SAINT</div>
</div>
<div class="title-box-2">
<div class="title-2">ANTONIEN</div>
</div>
<div class="desc">
Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and
adventure alike. It's a hidden gem for backcountry skiing in winter and boasts lush trails for hiking and mountain
biking during the warmer months.
</div>
<div class="cta">
<button class="bookmark">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path fill-rule="evenodd"
d="M6.32 2.577a49.255 49.255 0 0111.36 0c1.497.174 2.57 1.46 2.57 2.93V21a.75.75 0 01-1.085.67L12 18.089l-7.165 3.583A.75.75 0 013.75 21V5.507c0-1.47 1.073-2.756 2.57-2.93z"
clip-rule="evenodd" />
</svg>
</button>
<button class="discover">Discover Location</button>
</div>
</div>
<div class="details" id="details-odd">
<div class="place-box">
<div class="text">Switzerland Alps</div>
</div>
<div class="title-box-1">
<div class="title-1">SAINT </div>
</div>
<div class="title-box-2">
<div class="title-2">ANTONIEN</div>
</div>
<div class="desc">
Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and
adventure alike. It's a hidden gem for backcountry skiing in winter and boasts lush trails for hiking and mountain
biking during the warmer months.
</div>
<div class="cta">
<button class="bookmark">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path fill-rule="evenodd"
d="M6.32 2.577a49.255 49.255 0 0111.36 0c1.497.174 2.57 1.46 2.57 2.93V21a.75.75 0 01-1.085.67L12 18.089l-7.165 3.583A.75.75 0 013.75 21V5.507c0-1.47 1.073-2.756 2.57-2.93z"
clip-rule="evenodd" />
</svg>
</button>
<button class="discover">Discover Location</button>
</div>
</div>
<div class="pagination" id="pagination">
<div class="arrow arrow-left">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
</div>
<div class="arrow arrow-right">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</div>
<div class="progress-sub-container">
<div class="progress-sub-background">
<div class="progress-sub-foreground"></div>
</div>
</div>
<div class="slide-numbers" id="slide-numbers"></div>
</div>
<div class="cover"></div>
{% endblock %}
<!-- {% include 'footer.html' %}
<section id="views" class="block">
<section>
<h3>
Last 10 unique views
</h3>
{% for view in views.last %}
<p>
<span title="{{ view.country_name }}" class="unselectable">{{ view.country_flag.emoji }}</span>
<span title="{{ view.region }}">{{ view.region }}</span>
{% if view.referer is not null %}<samp>(<small title="{{ view.referer }}&lrm;">{{ view.referer
}}&lrm;</small>)</samp>{% endif %}
<span title="{{ view.useragent }}">{{ view['x-forwarded-for'] ?? view.ip }}</span>
<span title="{{ view.created }}" data-date-convert="title"
data-date-convert-format="year.month.day hour:minute:second">
<b data-date-convert="value">{{ view.created }}</b>
</span>
</p>
{% endfor %}
</section>
<section>
<p title="{{ views.day ?? 0 }}"><b class="unselectable">Day:</b><span>{{ views.day ?? 0 }}</span></p>
<p title="{{ views.week ?? 0 }}"><b class="unselectable">Week:</b><span>{{ views.week ?? 0 }}</span></p>
<p title="{{ views.month ?? 0 }}"><b class="unselectable">Month:</b><span>{{ views.month ?? 0 }}</span></p>
<p title="{{ views.all ?? 0 }}"><b class="unselectable">Total:</b><span>{{ views.all ?? 0 }}</span></p>
</section>
</section> -->
{% endblock %}
{% block js %}
{{ block('core_js') }}
{{ block('header_js') }}
{{ block('account_js') }}
<script src="/js/bloodchaos.js" defer></script>
{% endblock %}
{% block js_init %}
{{ block('core_js_init') }}
{{ block('header_js_init') }}
<script>
// Generate dates with user timezone
for (const element of document.body.querySelectorAll("[data-date-convert]")) {
// Enumerating elements to convert
// Initializing a format of convertation
const format =
element.getAttribute("data-date-convert-format") ?? "hour:minute:second";
if (element.getAttribute("data-date-convert") === "title") {
// Title
// Initializing a date with timezone
const date = new Date(element.getAttribute("title") * 1000);
// Initializing parts of the date
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDay();
let hour = date.getHours();
let minute = date.getMinutes();
let second = date.getSeconds();
// Convertation parts of the date to double symbols
month = (month < 10 ? "0" : "") + month;
day = (day < 10 ? "0" : "") + day;
hour = (hour < 10 ? "0" : "") + hour;
minute = (minute < 10 ? "0" : "") + minute;
second = (second < 10 ? "0" : "") + second;
if (format === "hour:minute:second")
element.setAttribute("title", `${hour}:${minute}:${second}`);
else if (format === "year.month.day hour:minute:second")
element.setAttribute(
"title",
`${year}.${month}.${day} ${hour}:${minute}:${second}`
);
} else {
// Value (implied)
// Initializin a date with timezone
const date = new Date(element.innerText * 1000);
// Initializing parts of the date
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDay();
let hour = date.getHours();
let minute = date.getMinutes();
let second = date.getSeconds();
// Convertation parts of the date to double symbols
month = (month < 10 ? "0" : "") + month;
day = (day < 10 ? "0" : "") + day;
hour = (hour < 10 ? "0" : "") + hour;
minute = (minute < 10 ? "0" : "") + minute;
second = (second < 10 ? "0" : "") + second;
if (format === "hour:minute:second")
element.innerText = `${hour}:${minute}:${second}`;
else if (format === "year.month.day hour:minute:second")
element.innerText = `${year}.${month}.${day} ${hour}:${minute}:${second}`;
}
}
</script>
{% endblock %}

View File

@ -0,0 +1,5 @@
{% block js %}
<script type="text/javascript" src="/js/js.cookie.min.js" defer></script>
<script type="text/javascript" src="/js/gsap.min.js" defer></script>
<script type="text/javascript" src="/js/carousel.js" defer></script>
{% endblock %}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace mirzaev\gavno\views;
use mirzaev\minimal\controller;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
/**
* Менеджер представлений
*
* @package mirzaev\gavno\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class manager extends controller
{
public function render(string $file, array $vars = []): ?string
{
// Генерация представления
return (new view(new FilesystemLoader(VIEWS)))->render($file, $vars);
}
}