feat: add SynthQuest game mode with World 1 (Waves) — 8 puzzle levels
Gamified synth learning inspired by Turing Complete. Progressive puzzle system teaches oscillators, waveforms, frequency, and mixing through hands-on module patching. Includes world map, level progression with 3-star rating, target audio playback, solution validation, and localStorage persistence. Sandbox mode still accessible via button. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
481
src/game/levels/world1.js
Normal file
481
src/game/levels/world1.js
Normal file
@@ -0,0 +1,481 @@
|
||||
/**
|
||||
* World 1 — "Ondas" (Waves)
|
||||
*
|
||||
* Teaches: oscillators, waveforms, frequency, mixing
|
||||
* 8 levels, progressive difficulty
|
||||
*/
|
||||
|
||||
export const WORLD_1 = {
|
||||
id: 'w1',
|
||||
name: 'Ondas',
|
||||
subtitle: 'Los bloques fundamentales del sonido',
|
||||
icon: '~',
|
||||
color: '#00e5ff',
|
||||
levels: [
|
||||
// ─────────────── LEVEL 1.1 ───────────────
|
||||
{
|
||||
id: 'w1-1',
|
||||
title: 'Tu Primer Sonido',
|
||||
subtitle: 'Conecta y escucha',
|
||||
description: 'Todo sonido en un sintetizador empieza con un oscilador. Un oscilador genera una onda que vibra a una frecuencia determinada. Conecta el oscilador a la salida para escuchar tu primer sonido.',
|
||||
concept: 'Un oscilador genera una onda. La salida (Output) envía el sonido a tus altavoces. Conecta la salida del oscilador al input de la salida arrastrando de un puerto a otro.',
|
||||
availableModules: [], // No new modules to add, just connect preplaced
|
||||
preplacedModules: [
|
||||
{ id: 1, type: 'oscillator', x: 100, y: 80, params: { waveform: 'sine', frequency: 440, detune: 0 }, locked: true },
|
||||
{ id: 2, type: 'output', x: 500, y: 100, params: { volume: -6 }, locked: true },
|
||||
],
|
||||
target: {
|
||||
// Simple sine at 440Hz
|
||||
build: [
|
||||
{ type: 'oscillator', params: { waveform: 'sine', frequency: 440 } },
|
||||
],
|
||||
duration: 2,
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
star: 1,
|
||||
name: 'Sonido conectado',
|
||||
desc: 'Conecta el oscilador a la salida',
|
||||
test: (mods, conns) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
if (!osc || !out) return false;
|
||||
return conns.some(c =>
|
||||
c.from.moduleId === osc.id && c.from.port === 'out' &&
|
||||
c.to.moduleId === out.id
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 2,
|
||||
name: 'Canal izquierdo',
|
||||
desc: 'Conecta al canal izquierdo (Left)',
|
||||
test: (mods, conns) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
if (!osc || !out) return false;
|
||||
return conns.some(c =>
|
||||
c.from.moduleId === osc.id && c.from.port === 'out' &&
|
||||
c.to.moduleId === out.id && c.to.port === 'left'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 3,
|
||||
name: 'Estéreo completo',
|
||||
desc: 'Conecta también al canal derecho (Right)',
|
||||
test: (mods, conns) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
if (!osc || !out) return false;
|
||||
const hasLeft = conns.some(c =>
|
||||
c.from.moduleId === osc.id && c.to.moduleId === out.id && c.to.port === 'left'
|
||||
);
|
||||
const hasRight = conns.some(c =>
|
||||
c.from.moduleId === osc.id && c.to.moduleId === out.id && c.to.port === 'right'
|
||||
);
|
||||
return hasLeft && hasRight;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ─────────────── LEVEL 1.2 ───────────────
|
||||
{
|
||||
id: 'w1-2',
|
||||
title: 'La Nota La',
|
||||
subtitle: 'Afinación: 440 Hz',
|
||||
description: 'La nota La central (A4) vibra a exactamente 440 Hz. Es la referencia universal para afinar instrumentos. Coloca un oscilador, ajústalo a 440 Hz y conéctalo a la salida.',
|
||||
concept: 'La frecuencia se mide en Hertz (Hz) — cuántas veces vibra la onda por segundo. 440 Hz = nota La. Usa el knob de frecuencia para ajustar.',
|
||||
availableModules: ['oscillator'],
|
||||
preplacedModules: [
|
||||
{ id: 1, type: 'output', x: 500, y: 100, params: { volume: -6 }, locked: true },
|
||||
],
|
||||
target: {
|
||||
build: [
|
||||
{ type: 'oscillator', params: { waveform: 'sine', frequency: 440 } },
|
||||
],
|
||||
duration: 2,
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
star: 1,
|
||||
name: 'Oscilador conectado',
|
||||
desc: 'Coloca un oscilador y conéctalo a la salida',
|
||||
test: (mods, conns) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
if (!osc || !out) return false;
|
||||
return conns.some(c =>
|
||||
c.from.moduleId === osc.id && c.to.moduleId === out.id
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 2,
|
||||
name: 'Frecuencia cercana',
|
||||
desc: 'Ajusta la frecuencia cerca de 440 Hz (±50 Hz)',
|
||||
test: (mods) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
if (!osc) return false;
|
||||
const f = osc.params.frequency ?? 440;
|
||||
return Math.abs(f - 440) <= 50;
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 3,
|
||||
name: 'Afinación perfecta',
|
||||
desc: 'Ajusta la frecuencia exacta a 440 Hz (±10 Hz)',
|
||||
test: (mods) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
if (!osc) return false;
|
||||
const f = osc.params.frequency ?? 440;
|
||||
return Math.abs(f - 440) <= 10;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ─────────────── LEVEL 1.3 ───────────────
|
||||
{
|
||||
id: 'w1-3',
|
||||
title: 'Onda Cuadrada',
|
||||
subtitle: 'El sonido 8-bit',
|
||||
description: 'La onda cuadrada es EL sonido de los videojuegos retro. El chip de sonido del NES y el Game Boy usaban ondas cuadradas. Escucha el objetivo y replica ese timbre.',
|
||||
concept: 'Cada forma de onda tiene un timbre diferente. La onda cuadrada suena brillante y "digital" porque contiene solo armónicos impares. Cambia el selector de onda (Wave) a "square".',
|
||||
availableModules: ['oscillator'],
|
||||
preplacedModules: [
|
||||
{ id: 1, type: 'output', x: 500, y: 100, params: { volume: -6 }, locked: true },
|
||||
],
|
||||
target: {
|
||||
build: [
|
||||
{ type: 'oscillator', params: { waveform: 'square', frequency: 440 } },
|
||||
],
|
||||
duration: 2,
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
star: 1,
|
||||
name: 'Sonido conectado',
|
||||
desc: 'Oscilador conectado a la salida',
|
||||
test: (mods, conns) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
return osc && out && conns.some(c =>
|
||||
c.from.moduleId === osc.id && c.to.moduleId === out.id
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 2,
|
||||
name: 'Onda cuadrada',
|
||||
desc: 'Cambia la forma de onda a square',
|
||||
test: (mods) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
return osc && (osc.params.waveform === 'square');
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 3,
|
||||
name: 'Match perfecto',
|
||||
desc: 'Cuadrada a 440 Hz (±10 Hz)',
|
||||
test: (mods) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
if (!osc) return false;
|
||||
return osc.params.waveform === 'square' && Math.abs((osc.params.frequency ?? 440) - 440) <= 10;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ─────────────── LEVEL 1.4 ───────────────
|
||||
{
|
||||
id: 'w1-4',
|
||||
title: 'Diente de Sierra',
|
||||
subtitle: 'Rica en armónicos',
|
||||
description: 'La onda diente de sierra (sawtooth) contiene TODOS los armónicos, lo que le da un sonido brillante y rico. Es la base de muchos sonidos de sintetizador clásicos, desde pads hasta leads.',
|
||||
concept: 'Sawtooth = todos los armónicos. Sine = solo la fundamental. Square = armónicos impares. Triangle = armónicos impares atenuados. Cada forma de onda tiene un carácter único.',
|
||||
availableModules: ['oscillator'],
|
||||
preplacedModules: [
|
||||
{ id: 1, type: 'output', x: 500, y: 100, params: { volume: -6 }, locked: true },
|
||||
],
|
||||
target: {
|
||||
build: [
|
||||
{ type: 'oscillator', params: { waveform: 'sawtooth', frequency: 440 } },
|
||||
],
|
||||
duration: 2,
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
star: 1,
|
||||
name: 'Sonido conectado',
|
||||
desc: 'Oscilador conectado a la salida',
|
||||
test: (mods, conns) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
return osc && out && conns.some(c =>
|
||||
c.from.moduleId === osc.id && c.to.moduleId === out.id
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 2,
|
||||
name: 'Diente de sierra',
|
||||
desc: 'Cambia la onda a sawtooth',
|
||||
test: (mods) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
return osc && osc.params.waveform === 'sawtooth';
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 3,
|
||||
name: 'Match perfecto',
|
||||
desc: 'Sawtooth a 440 Hz (±10 Hz)',
|
||||
test: (mods) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
if (!osc) return false;
|
||||
return osc.params.waveform === 'sawtooth' && Math.abs((osc.params.frequency ?? 440) - 440) <= 10;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ─────────────── LEVEL 1.5 ───────────────
|
||||
{
|
||||
id: 'w1-5',
|
||||
title: 'La Octava',
|
||||
subtitle: 'Doble o mitad',
|
||||
description: 'Una octava es la relación más fundamental en la música. Cuando doblas la frecuencia, subes una octava. Cuando la divides por dos, bajas una octava. Si La4 es 440 Hz, La3 es 220 Hz.',
|
||||
concept: 'Octava arriba = frecuencia × 2. Octava abajo = frecuencia ÷ 2. Ejemplo: A4=440Hz, A3=220Hz, A5=880Hz. Ajusta el knob a 220 Hz para bajar una octava.',
|
||||
availableModules: ['oscillator'],
|
||||
preplacedModules: [
|
||||
{ id: 1, type: 'output', x: 500, y: 100, params: { volume: -6 }, locked: true },
|
||||
],
|
||||
target: {
|
||||
build: [
|
||||
{ type: 'oscillator', params: { waveform: 'sine', frequency: 220 } },
|
||||
],
|
||||
duration: 2,
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
star: 1,
|
||||
name: 'Sonido conectado',
|
||||
desc: 'Oscilador conectado a la salida',
|
||||
test: (mods, conns) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
return osc && out && conns.some(c =>
|
||||
c.from.moduleId === osc.id && c.to.moduleId === out.id
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 2,
|
||||
name: 'Octava baja',
|
||||
desc: 'Frecuencia cercana a 220 Hz (±30 Hz)',
|
||||
test: (mods) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
if (!osc) return false;
|
||||
return Math.abs((osc.params.frequency ?? 440) - 220) <= 30;
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 3,
|
||||
name: 'Afinación perfecta',
|
||||
desc: 'Exactamente 220 Hz (±10 Hz)',
|
||||
test: (mods) => {
|
||||
const osc = mods.find(m => m.type === 'oscillator');
|
||||
if (!osc) return false;
|
||||
return Math.abs((osc.params.frequency ?? 440) - 220) <= 10;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ─────────────── LEVEL 1.6 ───────────────
|
||||
{
|
||||
id: 'w1-6',
|
||||
title: 'Dos Voces',
|
||||
subtitle: 'La quinta perfecta',
|
||||
description: 'Mezclar dos frecuencias crea armonía. La relación 3:2 entre dos notas es la "quinta perfecta" — el intervalo más consonante después de la octava. Mezcla 440 Hz con 660 Hz.',
|
||||
concept: 'Un Mixer combina varias señales de audio en una sola. Conecta varios osciladores a las entradas del mixer, y la salida del mixer al output. 440×1.5 = 660 Hz (quinta perfecta).',
|
||||
availableModules: ['oscillator', 'mixer'],
|
||||
preplacedModules: [
|
||||
{ id: 1, type: 'output', x: 700, y: 140, params: { volume: -6 }, locked: true },
|
||||
],
|
||||
target: {
|
||||
build: [
|
||||
{ type: 'oscillator', params: { waveform: 'sine', frequency: 440 } },
|
||||
{ type: 'oscillator', params: { waveform: 'sine', frequency: 660 } },
|
||||
],
|
||||
duration: 2.5,
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
star: 1,
|
||||
name: 'Dos osciladores',
|
||||
desc: 'Coloca 2 osciladores y un mixer, conectados a la salida',
|
||||
test: (mods, conns) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator');
|
||||
const mixer = mods.find(m => m.type === 'mixer');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
if (oscs.length < 2 || !mixer || !out) return false;
|
||||
// Check mixer → output
|
||||
const mixerToOut = conns.some(c => c.from.moduleId === mixer.id && c.to.moduleId === out.id);
|
||||
// Check at least one osc → mixer
|
||||
const oscToMixer = oscs.some(o => conns.some(c => c.from.moduleId === o.id && c.to.moduleId === mixer.id));
|
||||
return mixerToOut && oscToMixer;
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 2,
|
||||
name: 'Ambos conectados',
|
||||
desc: 'Ambos osciladores al mixer, mixer a la salida',
|
||||
test: (mods, conns) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator');
|
||||
const mixer = mods.find(m => m.type === 'mixer');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
if (oscs.length < 2 || !mixer || !out) return false;
|
||||
const mixerToOut = conns.some(c => c.from.moduleId === mixer.id && c.to.moduleId === out.id);
|
||||
const bothToMixer = oscs.every(o => conns.some(c => c.from.moduleId === o.id && c.to.moduleId === mixer.id));
|
||||
return mixerToOut && bothToMixer;
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 3,
|
||||
name: 'Quinta perfecta',
|
||||
desc: 'Frecuencias a ~440 Hz y ~660 Hz (±20 Hz)',
|
||||
test: (mods, conns) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator');
|
||||
if (oscs.length < 2) return false;
|
||||
const freqs = oscs.map(o => o.params.frequency ?? 440).sort((a, b) => a - b);
|
||||
return Math.abs(freqs[0] - 440) <= 20 && Math.abs(freqs[1] - 660) <= 20;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ─────────────── LEVEL 1.7 ───────────────
|
||||
{
|
||||
id: 'w1-7',
|
||||
title: 'El Unísono Gordo',
|
||||
subtitle: 'Detune = grosor',
|
||||
description: 'Los sintetizadores analógicos clásicos nunca estaban perfectamente afinados, y eso les daba un sonido "gordo". Desafinar ligeramente dos osciladores crea un efecto de coro natural. Usa dos sierras con un ligero detune.',
|
||||
concept: 'Detune = desafinación en "cents" (centésimas de semitono). Un detune de +7 a +12 cents crea un unísono gordo sin que suene desafinado. Usa dos osciladores sawtooth a la misma frecuencia pero con detune diferente.',
|
||||
availableModules: ['oscillator', 'mixer'],
|
||||
preplacedModules: [
|
||||
{ id: 1, type: 'output', x: 700, y: 140, params: { volume: -6 }, locked: true },
|
||||
],
|
||||
target: {
|
||||
build: [
|
||||
{ type: 'oscillator', params: { waveform: 'sawtooth', frequency: 440, detune: -7 } },
|
||||
{ type: 'oscillator', params: { waveform: 'sawtooth', frequency: 440, detune: 7 } },
|
||||
],
|
||||
duration: 2.5,
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
star: 1,
|
||||
name: 'Dos sierras mezcladas',
|
||||
desc: 'Dos osciladores sawtooth conectados al mixer y a la salida',
|
||||
test: (mods, conns) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator');
|
||||
const mixer = mods.find(m => m.type === 'mixer');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
if (oscs.length < 2 || !mixer || !out) return false;
|
||||
const bothSaw = oscs.every(o => o.params.waveform === 'sawtooth');
|
||||
const mixToOut = conns.some(c => c.from.moduleId === mixer.id && c.to.moduleId === out.id);
|
||||
return bothSaw && mixToOut;
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 2,
|
||||
name: 'Con detune',
|
||||
desc: 'Al menos un oscilador tiene detune distinto de 0',
|
||||
test: (mods) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator' && m.params.waveform === 'sawtooth');
|
||||
if (oscs.length < 2) return false;
|
||||
const detunes = oscs.map(o => o.params.detune ?? 0);
|
||||
return detunes.some(d => Math.abs(d) > 2);
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 3,
|
||||
name: 'Unísono perfecto',
|
||||
desc: 'Detune entre ±5 y ±15 cents (sutil pero gordo)',
|
||||
test: (mods) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator' && m.params.waveform === 'sawtooth');
|
||||
if (oscs.length < 2) return false;
|
||||
const detunes = oscs.map(o => Math.abs(o.params.detune ?? 0)).sort((a, b) => a - b);
|
||||
// One should be near 0 or opposite, total spread should be 5-30 cents
|
||||
const spread = Math.abs(detunes[detunes.length - 1] - detunes[0]);
|
||||
const maxDetune = Math.max(...oscs.map(o => Math.abs(o.params.detune ?? 0)));
|
||||
return maxDetune >= 5 && maxDetune <= 50;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ─────────────── LEVEL 1.8: BOSS ───────────────
|
||||
{
|
||||
id: 'w1-8',
|
||||
title: 'Acorde Mayor',
|
||||
subtitle: 'BOSS: Do Mayor',
|
||||
description: 'Pon todo junto. Un acorde mayor se construye con tres notas: la fundamental, la tercera mayor, y la quinta. Do Mayor = C4 (262 Hz) + E4 (330 Hz) + G4 (392 Hz). Constrúyelo.',
|
||||
concept: 'C4=262Hz, E4=330Hz, G4=392Hz. Necesitas 3 osciladores, un mixer para combinarlos, y la salida. Las frecuencias no tienen que ser exactas — usa tu oído para comparar con el sonido objetivo.',
|
||||
availableModules: ['oscillator', 'mixer'],
|
||||
preplacedModules: [
|
||||
{ id: 1, type: 'output', x: 800, y: 160, params: { volume: -6 }, locked: true },
|
||||
],
|
||||
target: {
|
||||
build: [
|
||||
{ type: 'oscillator', params: { waveform: 'sine', frequency: 262 } },
|
||||
{ type: 'oscillator', params: { waveform: 'sine', frequency: 330 } },
|
||||
{ type: 'oscillator', params: { waveform: 'sine', frequency: 392 } },
|
||||
],
|
||||
duration: 3,
|
||||
},
|
||||
checks: [
|
||||
{
|
||||
star: 1,
|
||||
name: 'Tres voces',
|
||||
desc: 'Tres osciladores conectados al mixer y a la salida',
|
||||
test: (mods, conns) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator');
|
||||
const mixer = mods.find(m => m.type === 'mixer');
|
||||
const out = mods.find(m => m.type === 'output');
|
||||
if (oscs.length < 3 || !mixer || !out) return false;
|
||||
const mixToOut = conns.some(c => c.from.moduleId === mixer.id && c.to.moduleId === out.id);
|
||||
const count = oscs.filter(o => conns.some(c => c.from.moduleId === o.id && c.to.moduleId === mixer.id)).length;
|
||||
return mixToOut && count >= 3;
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 2,
|
||||
name: 'Acorde cercano',
|
||||
desc: 'Frecuencias cerca de C4, E4, G4 (±30 Hz)',
|
||||
test: (mods) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator');
|
||||
if (oscs.length < 3) return false;
|
||||
const freqs = oscs.map(o => o.params.frequency ?? 440).sort((a, b) => a - b);
|
||||
const targets = [262, 330, 392];
|
||||
return targets.every((t, i) => Math.abs(freqs[i] - t) <= 30);
|
||||
},
|
||||
},
|
||||
{
|
||||
star: 3,
|
||||
name: 'Do Mayor perfecto',
|
||||
desc: 'C4=262, E4=330, G4=392 Hz (±10 Hz)',
|
||||
test: (mods) => {
|
||||
const oscs = mods.filter(m => m.type === 'oscillator');
|
||||
if (oscs.length < 3) return false;
|
||||
const freqs = oscs.map(o => o.params.frequency ?? 440).sort((a, b) => a - b);
|
||||
const targets = [262, 330, 392];
|
||||
return targets.every((t, i) => Math.abs(freqs[i] - t) <= 10);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user