Reemplazar Wave Grid por Lissajous Curve 3D
Cambiar figura "Wave Grid" (malla ondeante) por curva de Lissajous 3D, con ecuaciones paramétricas más hipnóticas y resultónas visualmente. ## Cambios Principales **Archivos renombrados:** - `wave_grid_shape.h/cpp` → `lissajous_shape.h/cpp` - Clase `WaveGridShape` → `LissajousShape` **Ecuaciones implementadas:** - x(t) = A * sin(3t + φx) - Frecuencia 3 en X - y(t) = A * sin(2t) - Frecuencia 2 en Y - z(t) = A * sin(t + φz) - Frecuencia 1 en Z - Ratio 3:2:1 produce patrón de "trenza elegante" **Animación:** - Rotación global dual (ejes X/Y) - Animación de fase continua (morphing) - Más dinámica y orgánica que Wave Grid **defines.h:** - `WAVE_GRID_*` → `LISSAJOUS_*` constantes - `ShapeType::WAVE_GRID` → `ShapeType::LISSAJOUS` **engine.cpp:** - Actualizado include y instanciación - Arrays de figuras DEMO actualizados - Tecla W ahora activa Lissajous ## Resultado Curva 3D paramétrica hipnótica con patrón entrelazado, rotación continua y morphing de fase. Más espectacular que el grid ondeante anterior. 🌀 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -30,10 +30,10 @@
|
||||
#include "shapes/cylinder_shape.h" // for CylinderShape
|
||||
#include "shapes/helix_shape.h" // for HelixShape
|
||||
#include "shapes/icosahedron_shape.h" // for IcosahedronShape
|
||||
#include "shapes/lissajous_shape.h" // for LissajousShape
|
||||
#include "shapes/png_shape.h" // for PNGShape
|
||||
#include "shapes/sphere_shape.h" // for SphereShape
|
||||
#include "shapes/torus_shape.h" // for TorusShape
|
||||
#include "shapes/wave_grid_shape.h" // for WaveGridShape
|
||||
|
||||
// getExecutableDirectory() ya está definido en defines.h como inline
|
||||
|
||||
@@ -48,8 +48,8 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
||||
}
|
||||
|
||||
int num_displays = 0;
|
||||
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
|
||||
const auto *dm = (displays && num_displays > 0) ? SDL_GetCurrentDisplayMode(displays[0]) : nullptr;
|
||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||
const auto* dm = (displays && num_displays > 0) ? SDL_GetCurrentDisplayMode(displays[0]) : nullptr;
|
||||
|
||||
int screen_w = dm ? dm->w : 1920; // Fallback si falla
|
||||
int screen_h = dm ? dm->h - WINDOW_DECORATION_HEIGHT : 1080;
|
||||
@@ -132,9 +132,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
||||
std::string balls_dir = resources_dir + "/data/balls";
|
||||
|
||||
struct TextureInfo {
|
||||
std::string name;
|
||||
std::shared_ptr<Texture> texture;
|
||||
int width;
|
||||
std::string name;
|
||||
std::shared_ptr<Texture> texture;
|
||||
int width;
|
||||
};
|
||||
std::vector<TextureInfo> texture_files;
|
||||
|
||||
@@ -162,7 +162,7 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
||||
// Filtrar solo los recursos en balls/ con extensión .png
|
||||
for (const auto& resource : pack_resources) {
|
||||
if (resource.substr(0, 6) == "balls/" && resource.substr(resource.size() - 4) == ".png") {
|
||||
std::string tex_name = resource.substr(6); // Quitar "balls/"
|
||||
std::string tex_name = resource.substr(6); // Quitar "balls/"
|
||||
std::string name = tex_name.substr(0, tex_name.find('.')); // Quitar extensión
|
||||
|
||||
auto texture = std::make_shared<Texture>(renderer_, resource);
|
||||
@@ -175,10 +175,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
||||
}
|
||||
|
||||
// Ordenar por tamaño (grande → pequeño): big(16) → normal(10) → small(6) → tiny(4)
|
||||
std::sort(texture_files.begin(), texture_files.end(),
|
||||
[](const TextureInfo& a, const TextureInfo& b) {
|
||||
return a.width > b.width; // Descendente por tamaño
|
||||
});
|
||||
std::sort(texture_files.begin(), texture_files.end(), [](const TextureInfo& a, const TextureInfo& b) {
|
||||
return a.width > b.width; // Descendente por tamaño
|
||||
});
|
||||
|
||||
// Guardar texturas ya cargadas en orden (0=big, 1=normal, 2=small, 3=tiny)
|
||||
for (const auto& info : texture_files) {
|
||||
@@ -405,7 +404,7 @@ void Engine::handleEvents() {
|
||||
break;
|
||||
|
||||
case SDLK_W:
|
||||
activateShape(ShapeType::WAVE_GRID);
|
||||
activateShape(ShapeType::LISSAJOUS);
|
||||
break;
|
||||
|
||||
case SDLK_E:
|
||||
@@ -1461,41 +1460,54 @@ void Engine::initializeDynamicThemes() {
|
||||
dynamic_themes_[0] = {
|
||||
"SUNRISE",
|
||||
"AMANECER",
|
||||
255, 200, 100, // Color texto: amarillo cálido
|
||||
255,
|
||||
200,
|
||||
100, // Color texto: amarillo cálido
|
||||
{
|
||||
// Keyframe 0: Noche oscura (estado inicial)
|
||||
{
|
||||
20.0f / 255.0f, 25.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: azul medianoche
|
||||
10.0f / 255.0f, 10.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||
{{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160},
|
||||
{95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}}, // Pelotas azules tenues
|
||||
0.0f // Sin transición (estado inicial)
|
||||
20.0f / 255.0f,
|
||||
25.0f / 255.0f,
|
||||
60.0f / 255.0f, // Fondo superior: azul medianoche
|
||||
10.0f / 255.0f,
|
||||
10.0f / 255.0f,
|
||||
30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||
{{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160}, {95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}}, // Pelotas azules tenues
|
||||
0.0f // Sin transición (estado inicial)
|
||||
},
|
||||
// Keyframe 1: Alba naranja-rosa
|
||||
{
|
||||
180.0f / 255.0f, 100.0f / 255.0f, 120.0f / 255.0f, // Fondo superior: naranja-rosa
|
||||
255.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo inferior: naranja cálido
|
||||
{{255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90},
|
||||
{255, 190, 110}, {255, 170, 95}, {255, 185, 105}, {255, 165, 88}}, // Pelotas naranjas
|
||||
4.0f // 4 segundos para llegar aquí
|
||||
180.0f / 255.0f,
|
||||
100.0f / 255.0f,
|
||||
120.0f / 255.0f, // Fondo superior: naranja-rosa
|
||||
255.0f / 255.0f,
|
||||
140.0f / 255.0f,
|
||||
100.0f / 255.0f, // Fondo inferior: naranja cálido
|
||||
{{255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90}, {255, 190, 110}, {255, 170, 95}, {255, 185, 105}, {255, 165, 88}}, // Pelotas naranjas
|
||||
4.0f // 4 segundos para llegar aquí
|
||||
},
|
||||
// Keyframe 2: Día brillante amarillo
|
||||
{
|
||||
255.0f / 255.0f, 240.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: amarillo claro
|
||||
255.0f / 255.0f, 255.0f / 255.0f, 220.0f / 255.0f, // Fondo inferior: amarillo muy claro
|
||||
{{255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190},
|
||||
{255, 255, 210}, {255, 255, 185}, {255, 255, 205}, {255, 255, 195}}, // Pelotas amarillas brillantes
|
||||
3.0f // 3 segundos para llegar aquí
|
||||
255.0f / 255.0f,
|
||||
240.0f / 255.0f,
|
||||
180.0f / 255.0f, // Fondo superior: amarillo claro
|
||||
255.0f / 255.0f,
|
||||
255.0f / 255.0f,
|
||||
220.0f / 255.0f, // Fondo inferior: amarillo muy claro
|
||||
{{255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190}, {255, 255, 210}, {255, 255, 185}, {255, 255, 205}, {255, 255, 195}}, // Pelotas amarillas brillantes
|
||||
3.0f // 3 segundos para llegar aquí
|
||||
},
|
||||
// Keyframe 3: Vuelta a noche (para loop)
|
||||
{
|
||||
20.0f / 255.0f, 25.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: azul medianoche
|
||||
10.0f / 255.0f, 10.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||
{{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160},
|
||||
{95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}}, // Pelotas azules tenues
|
||||
5.0f // 5 segundos para volver a noche
|
||||
}
|
||||
},
|
||||
20.0f / 255.0f,
|
||||
25.0f / 255.0f,
|
||||
60.0f / 255.0f, // Fondo superior: azul medianoche
|
||||
10.0f / 255.0f,
|
||||
10.0f / 255.0f,
|
||||
30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||
{{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160}, {95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}}, // Pelotas azules tenues
|
||||
5.0f // 5 segundos para volver a noche
|
||||
}},
|
||||
true // Loop = true
|
||||
};
|
||||
|
||||
@@ -1505,33 +1517,43 @@ void Engine::initializeDynamicThemes() {
|
||||
dynamic_themes_[1] = {
|
||||
"OCEAN WAVES",
|
||||
"OLAS OCEANICAS",
|
||||
100, 220, 255, // Color texto: cian claro
|
||||
100,
|
||||
220,
|
||||
255, // Color texto: cian claro
|
||||
{
|
||||
// Keyframe 0: Profundidad oceánica (azul oscuro)
|
||||
{
|
||||
20.0f / 255.0f, 50.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: azul marino
|
||||
10.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||
{{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175},
|
||||
{65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}}, // Pelotas azul oscuro
|
||||
0.0f // Estado inicial
|
||||
20.0f / 255.0f,
|
||||
50.0f / 255.0f,
|
||||
100.0f / 255.0f, // Fondo superior: azul marino
|
||||
10.0f / 255.0f,
|
||||
30.0f / 255.0f,
|
||||
60.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||
{{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175}, {65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}}, // Pelotas azul oscuro
|
||||
0.0f // Estado inicial
|
||||
},
|
||||
// Keyframe 1: Aguas poco profundas (turquesa brillante)
|
||||
{
|
||||
100.0f / 255.0f, 200.0f / 255.0f, 230.0f / 255.0f, // Fondo superior: turquesa claro
|
||||
50.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo inferior: turquesa medio
|
||||
{{100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250},
|
||||
{105, 225, 255}, {98, 218, 248}, {102, 222, 252}, {92, 212, 242}}, // Pelotas turquesa brillante
|
||||
4.0f // 4 segundos para llegar
|
||||
100.0f / 255.0f,
|
||||
200.0f / 255.0f,
|
||||
230.0f / 255.0f, // Fondo superior: turquesa claro
|
||||
50.0f / 255.0f,
|
||||
150.0f / 255.0f,
|
||||
200.0f / 255.0f, // Fondo inferior: turquesa medio
|
||||
{{100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250}, {105, 225, 255}, {98, 218, 248}, {102, 222, 252}, {92, 212, 242}}, // Pelotas turquesa brillante
|
||||
4.0f // 4 segundos para llegar
|
||||
},
|
||||
// Keyframe 2: Vuelta a profundidad (para loop)
|
||||
{
|
||||
20.0f / 255.0f, 50.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: azul marino
|
||||
10.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||
{{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175},
|
||||
{65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}}, // Pelotas azul oscuro
|
||||
4.0f // 4 segundos para volver
|
||||
}
|
||||
},
|
||||
20.0f / 255.0f,
|
||||
50.0f / 255.0f,
|
||||
100.0f / 255.0f, // Fondo superior: azul marino
|
||||
10.0f / 255.0f,
|
||||
30.0f / 255.0f,
|
||||
60.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||
{{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175}, {65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}}, // Pelotas azul oscuro
|
||||
4.0f // 4 segundos para volver
|
||||
}},
|
||||
true // Loop = true
|
||||
};
|
||||
|
||||
@@ -1541,33 +1563,43 @@ void Engine::initializeDynamicThemes() {
|
||||
dynamic_themes_[2] = {
|
||||
"NEON PULSE",
|
||||
"PULSO NEON",
|
||||
255, 60, 255, // Color texto: magenta brillante
|
||||
255,
|
||||
60,
|
||||
255, // Color texto: magenta brillante
|
||||
{
|
||||
// Keyframe 0: Apagado (negro)
|
||||
{
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: negro
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||
{{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48},
|
||||
{42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}}, // Pelotas grises muy oscuras
|
||||
0.0f // Estado inicial
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f, // Fondo superior: negro
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f, // Fondo inferior: negro
|
||||
{{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48}, {42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}}, // Pelotas grises muy oscuras
|
||||
0.0f // Estado inicial
|
||||
},
|
||||
// Keyframe 1: Encendido (neón cian-magenta)
|
||||
{
|
||||
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: azul oscuro
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||
{{0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255},
|
||||
{50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255}}, // Pelotas neón vibrante
|
||||
1.5f // 1.5 segundos para encender (rápido)
|
||||
20.0f / 255.0f,
|
||||
20.0f / 255.0f,
|
||||
40.0f / 255.0f, // Fondo superior: azul oscuro
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f, // Fondo inferior: negro
|
||||
{{0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255}, {50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255}}, // Pelotas neón vibrante
|
||||
1.5f // 1.5 segundos para encender (rápido)
|
||||
},
|
||||
// Keyframe 2: Vuelta a apagado (para loop ping-pong)
|
||||
{
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: negro
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||
{{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48},
|
||||
{42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}}, // Pelotas grises muy oscuras
|
||||
1.5f // 1.5 segundos para apagar
|
||||
}
|
||||
},
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f, // Fondo superior: negro
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f,
|
||||
0.0f / 255.0f, // Fondo inferior: negro
|
||||
{{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48}, {42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}}, // Pelotas grises muy oscuras
|
||||
1.5f // 1.5 segundos para apagar
|
||||
}},
|
||||
true // Loop = true
|
||||
};
|
||||
}
|
||||
@@ -1641,7 +1673,7 @@ void Engine::updateDynamicTheme() {
|
||||
target_keyframe_index_ = 0; // Volver al inicio
|
||||
} else {
|
||||
target_keyframe_index_ = theme.keyframes.size() - 1; // Quedarse en el último
|
||||
dynamic_theme_active_ = false; // Detener animación
|
||||
dynamic_theme_active_ = false; // Detener animación
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1925,7 +1957,7 @@ void Engine::performDemoAction(bool is_lite) {
|
||||
// Activar figura 3D (25%) - PNG_SHAPE excluido (reservado para Logo Mode)
|
||||
accumulated_weight += DEMO_LITE_WEIGHT_SHAPE;
|
||||
if (random_value < accumulated_weight) {
|
||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||
int shape_index = rand() % 8;
|
||||
activateShape(shapes[shape_index]);
|
||||
return;
|
||||
@@ -1968,7 +2000,7 @@ void Engine::performDemoAction(bool is_lite) {
|
||||
// Activar figura 3D (20%) - PNG_SHAPE excluido (reservado para Logo Mode)
|
||||
accumulated_weight += DEMO_WEIGHT_SHAPE;
|
||||
if (random_value < accumulated_weight) {
|
||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||
int shape_index = rand() % 8;
|
||||
activateShape(shapes[shape_index]);
|
||||
return;
|
||||
@@ -2063,7 +2095,7 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
|
||||
}
|
||||
} else {
|
||||
// Modo figura: elegir figura aleatoria (excluir PNG_SHAPE - es logo especial)
|
||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||
activateShape(shapes[rand() % 8]);
|
||||
}
|
||||
|
||||
@@ -2099,7 +2131,7 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
|
||||
}
|
||||
} else {
|
||||
// Modo figura: elegir figura aleatoria (excluir PNG_SHAPE - es logo especial)
|
||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||
activateShape(shapes[rand() % 8]);
|
||||
|
||||
// 5. Profundidad (solo si estamos en figura)
|
||||
@@ -2403,8 +2435,8 @@ void Engine::activateShape(ShapeType type) {
|
||||
case ShapeType::TORUS:
|
||||
active_shape_ = std::make_unique<TorusShape>();
|
||||
break;
|
||||
case ShapeType::WAVE_GRID:
|
||||
active_shape_ = std::make_unique<WaveGridShape>();
|
||||
case ShapeType::LISSAJOUS:
|
||||
active_shape_ = std::make_unique<LissajousShape>();
|
||||
break;
|
||||
case ShapeType::CYLINDER:
|
||||
active_shape_ = std::make_unique<CylinderShape>();
|
||||
|
||||
Reference in New Issue
Block a user