From 05d422ef4a576ddd92be6f94f97d9c715ff6373a Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 6 Aug 2026 22:10:42 +0200 Subject: [PATCH] docs(web): cuatro paginas del sonido, escritas para quien llega de nuevas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correccion del autor sobre la primera version: «la redaccion esta mal, empieza como si la gente supiera ya que es lo que has pasado, la redaccion tiene que ser didactica para un nuevo desarrollador». Tenia razon — estaban escritas como notas de version para quien vivio el rediseño: se abrian presumiendo el vocabulario y presumian de cuantas reglas se habian borrado, que es mi historia, no documentacion. Reescritas desde cero, en ese orden: primero la escena («guardas un documento y oyes un repique»), luego el vocabulario construido pieza a pieza (familia, verbo, intent) y solo entonces la maquinaria. Cada concepto se explica antes de usar su palabra. /uix/docs/sound como suena una interfaz — el modelo, con la resolucion en vivo (familia + verbo + intent) /uix/docs/sound/catalogo cada sonido que existe, audible, con la razon de por que suena asi y que significa cada eje /uix/docs/sound/packs traer tu propia voz: sintesis, wav, mp3, registrar nombres nuevos, cambiar en caliente /uix/docs/sound/gestos por que un arrastre suena por repeticion, con un simulador de tres velocidades para oirlo DERIVADAS DEL CODIGO: los sonidos, familias, intents y verbos salen de `SEMA_MAP` y del resolver real. Solo la prosa esta escrita a mano, asi que añadir una entrada al catalogo la hace aparecer con sus medidas. Lo historico no desaparece, cambia de sitio: donde aporta —por que los sonidos son tan distintos entre si, que se gana y que se pierde con el trinquete— va al final de su seccion como razon, nunca como apertura. VERIFICADO: las cuatro responden 200 en SSR con sus secciones · check sin errores nuevos en las paginas. ⚠️ Sigo sin poder mirarlas: el panel del navegador no compone en esta sesion, asi que la composicion visual queda pendiente de tu ojo. Co-Authored-By: Claude Opus 5 --- web/routes/uix/+layout@.svelte | 9 +- .../uix/components/chat-message/+page.svelte | 2 +- web/routes/uix/docs/sound/+page.svelte | 242 ++++++++++-------- .../uix/docs/sound/catalogo/+page.svelte | 213 +++++++++++++++ web/routes/uix/docs/sound/gestos/+page.svelte | 114 +++++++++ web/routes/uix/docs/sound/packs/+page.svelte | 164 ++++++++++++ web/routes/uix/uix.css | 46 ++++ 7 files changed, 679 insertions(+), 111 deletions(-) create mode 100644 web/routes/uix/docs/sound/catalogo/+page.svelte create mode 100644 web/routes/uix/docs/sound/gestos/+page.svelte create mode 100644 web/routes/uix/docs/sound/packs/+page.svelte diff --git a/web/routes/uix/+layout@.svelte b/web/routes/uix/+layout@.svelte index f26ece102..bb8e917e7 100644 --- a/web/routes/uix/+layout@.svelte +++ b/web/routes/uix/+layout@.svelte @@ -242,8 +242,13 @@ }, { kind: 'group', - heading: 'El sistema perceptual', - items: [{ slug: '/uix/docs/sound', label: 'El sonido' }] + heading: 'El sonido', + items: [ + { slug: '/uix/docs/sound', label: 'Cómo suena una interfaz' }, + { slug: '/uix/docs/sound/catalogo', label: 'El catálogo' }, + { slug: '/uix/docs/sound/packs', label: 'Sound packs' }, + { slug: '/uix/docs/sound/gestos', label: 'Gestos' } + ] }, { kind: 'group', diff --git a/web/routes/uix/components/chat-message/+page.svelte b/web/routes/uix/components/chat-message/+page.svelte index 8105af708..5b08709ae 100644 --- a/web/routes/uix/components/chat-message/+page.svelte +++ b/web/routes/uix/components/chat-message/+page.svelte @@ -502,7 +502,7 @@ Three events: commit-select-react / commit-unselect-react (both affirm — the undo matrix: removing a reaction is as active a choice as adding it) and commit-submit-retry (neutral). The pack keeps - reactions subtle (high-frequency doctrine): form.commit.subtle + a light + reactions subtle (high-frequency doctrine): commit.subtle + a light tap.

/** - * `/uix/docs/sound` — how the sound channel works. + * `/uix/docs/sound` — the entry point for the sound channel. * - * DERIVED, NEVER TRANSCRIBED: every table reads the live `SEMA_MAP` and the - * real `resolveSignature`. - * - * Audible: the ▶ buttons emit through the real engine, so the topbar's Sound - * switch silences them (the layout passes `preferences` to the channels). + * Written for someone who has never seen this framework: it introduces the + * vocabulary (family, verb, intent) BEFORE using it, and only names the + * machinery once the reader needs the word. Tables are derived from the + * live `SEMA_MAP` and the real resolver, so they cannot go stale. */ import { getActiveUix } from '$active-uix'; import { SEMA_MAP, resolveSignature } from '$uix/sema'; @@ -14,8 +13,6 @@ const uix = getActiveUix(); const NAMES = Object.keys(SEMA_MAP.sounds); - const BASES = NAMES.filter((n) => !n.includes('.')); - const VARIANTS = NAMES.filter((n) => n.includes('.')); const FAMILIES = Object.keys(SEMA_MAP.families); const INTENTS = Object.keys(SEMA_MAP.intents); @@ -24,12 +21,10 @@ let intent = $state('neutral'); let stage = $state(null); - /** The verbs the map gives a cell of its own, for the family in hand. */ const verbCells = $derived( Object.keys(SEMA_MAP.families[family as never]?.sounds ?? {}).filter((k) => k !== 'default') ); - /** Step 1 — which NAME does this occurrence get? */ const chosenName = $derived.by(() => { const table = SEMA_MAP.families[family as never]?.sounds as | Record @@ -37,7 +32,6 @@ return (verb ? table?.[verb] : undefined) ?? table?.default; }); - /** Step 2 — which ENTRY does the intent select? */ const chosenEntry = $derived.by(() => { if (!chosenName) return undefined; const withIntent = `${chosenName}.${intent}`; @@ -62,8 +56,6 @@ ).sound; }); - const AXES = ['pitch', 'centroid', 'duration', 'contour', 'roughness', 'gain'] as const; - function play(name?: string) { if (!stage) return; void uix.events?.emit({ @@ -79,73 +71,96 @@
Sema · el canal del sonido
-

Un evento y un intent eligen un sonido. Y ya está.

+

Cómo suena una interfaz.

- Nada modula nada. El sonido no se deforma, se elige: hay un catálogo de sonidos - completos, y el par (evento, intent) señala uno. Si no existe la variante, se desprecia el - intent. Si no hay ninguno, no suena nada. Esta página lee - SEMA_MAP y el resolver reales — no es una transcripción. + Guardas un documento y oyes un pequeño repique. Lo borras y oyes algo más grave. Nadie escribió + «reproduce este fichero» en el botón de guardar: el componente sólo + describió lo que estaba pasando, y el sistema eligió el sonido. Esta página + explica cómo, desde cero.

-
- bases{BASES.length} - variantes{VARIANTS.length} - reglas en 71 packs30 -
-

1 · La regla entera

+

1 · Un componente describe, no decide

+

+ Cuando algo ocurre en un componente —se pulsa, se guarda, se abre un panel— ese componente declara qué clase de suceso es. No dice cómo suena, ni de qué color se pone, ni cómo se anima. Sólo lo describe, con tres + palabras: +

+
    +
  • + La familia — a qué clase de suceso pertenece. Hay + {FAMILIES.length}, y son un conjunto cerrado: contact (algo se ha tocado), + commit + (algo ha quedado hecho), signal (el sistema avisa), emerge (algo + aparece o desaparece), + shift (el marco cambia), handle (un gesto continuo), y dos más que no + suenan. +
  • +
  • + El verbo — qué se hizo exactamente dentro de esa familia: + save, delete, open, close… +
  • +
  • + El intent — la carga: ¿salió bien, es arriesgado, es peligroso, se ha + perdido algo? Hay {INTENTS.length}, empezando por neutral. +
  • +
{`nombre = per-emit ?? cascada-app ?? pack ?? morfo ?? familia[verbo] ?? familia.default
-sonido = pack[nombre.intent] ?? pack[nombre] ?? nada`}{`// El contrato de un componente. No hay ni un sonido aquí.
+{
+	name: 'commit-save',
+	semantic: { family: 'commit', verb: 'save', intent: 'neutral' }
+}`}

- Dos búsquedas. No hay capas que se pisen, ni deltas, ni aritmética. Y el caso normal no escribe nada: el mapa ya dice que un - commit hace tick y que un emerge con verbo - close hace close. Un pack sólo habla cuando - difiere — por eso de las ~165 reglas de sonido que había quedan - 30, en 16 de los 71 packs. + Esa separación es la idea entera: el componente sabe qué pasó, y el sistema sabe + a qué suena eso. Cambiar la voz de toda la aplicación no obliga a tocar ni un + componente.

-

2 · El catálogo

+

2 · El sistema elige — dos pasos

- Cada entrada es un sonido diseñado entero, no un modificador. Un guard exige - que dos entradas se separen en al menos dos ejes - perceptuales — es el test que faltaba cuando todos sonaban igual. + Con esa descripción en la mano, el sistema hace dos búsquedas y nada más.

-

Bases

-
- {#each BASES as name} - - {/each} -
-

Variantes por intent

-
- {#each VARIANTS as name} - - {/each} -

- Un pack que sólo trae bases funciona entero: donde no haya variante, el intent se ignora. Un - pack cuidado añade variantes sólo donde le importa. + Primero, el nombre. Cada familia tiene una tabla que dice a qué suena, y + puede afinar por verbo. Un commit suena a + tick; un emerge suena a open, salvo que el verbo sea + close, en cuyo caso suena a close. Un componente normal no escribe + nada de esto: ya está en el sistema. +

+

+ Después, el intent. Con el nombre ya elegido, se busca si existe una versión + de ese sonido para este intent. tick + threat + encuentra tick.threat, que es un sonido distinto: más grave, más rasposo y + descendente. Si esa versión no existe —por ejemplo + tick.affirm— el intent simplemente se ignora y suena el + tick normal. Y si no hubiera ni siquiera tick, no sonaría nada; el + silencio es una respuesta válida. +

+
{`nombre  =  la tabla de la familia, afinada por verbo
+sonido  =  catálogo[nombre.intent]  ??  catálogo[nombre]  ??  nada`}
+

+ Lo importante de este diseño: nada se deforma. Un intent no «hace más grave» + un sonido — elige otro, diseñado entero por separado. Es como funciona el + reconocimiento: distinguimos sonidos, no desplazamientos de parámetro.

-

3 · Pruébalo

+

3 · Míralo funcionar

- Elige familia, verbo e intent, y mira cómo se resuelve el nombre y qué entrada acaba sonando. + Elige una familia, un verbo y un intent, y observa las dos búsquedas. Para oírlo, enciende Sound + en el menú Semantics de la barra superior.

@@ -158,7 +173,7 @@ sonido = pack[nombre.intent] ?? pack[nombre] ?? nada`} verbo @@ -176,79 +191,90 @@ sonido = pack[nombre.intent] ?? pack[nombre] ?? nada`} - - - - + - + - - {#each AXES as axis} - - {/each} - - - - {#each AXES as axis} - - {/each} + +
pasoresultado
- 1 · el nombre - familia[verbo] ?? familia.default + Paso 1 — el nombre + lo dice la tabla de la familia {chosenName ?? '— esta familia no nombra sonido'}{chosenName ?? 'esta familia no suena'}
- 2 · la entrada - nombre.intent ?? nombre + Paso 2 — el sonido + ¿hay versión para este intent? {chosenEntry ?? '— no suena nada'} + {#if !chosenEntry} + no suena nada + {:else if chosenEntry.includes('.')} + {chosenEntry} — sí, hay versión propia + {:else} + {chosenEntry} — no hay versión, se ignora el intent + {/if} +
lo que suena{resolved ? (resolved[axis] ?? '—') : '—'}
eje{axis}Lo que se oye + {#if resolved} + {resolved.pitch} Hz · {resolved.duration} ms · {resolved.contour} + {#if resolved.roughness >= 0.2}· áspero{/if} + {:else} + silencio + {/if} +

- Prueba commit + threat: no es un tick con más aspereza, - es otro sonido — más grave, más rasposo y descendente. Y prueba - commit - + affirm: no hay variante, así que el intent se desprecia y suena el - tick de siempre. -

-
- -
-

4 · Los gestos suenan por repetición

-

- Un arrastre no calcula nada: emite a su propio ritmo, y cada emisión toca - step — 18 ms. La - velocidad del gesto es la velocidad del trinquete, como una rueda física. Eso - sustituyó a tres resolvers que sintetizaban el tono desde posición y velocidad, lo último que - quedaba de aritmética. Y la familia handle está exenta de la memoria de frecuencia: - si no, el anti-fatiga estrangularía el trinquete al primer arrastre. + Tres combinaciones que enseñan la regla entera: commit + + threat (hay versión, suena otra cosa) · commit + + affirm (no hay versión, se ignora el intent) · sustain + (esa familia no suena, y está bien).

-

5 · Nadie escribe parámetros

+

4 · Cuando un componente quiere otra cosa

- Ni un componente, ni un pack, ni un tema, ni la app. El tipo acepta un - nombre o SILENT. Si un producto necesita un sonido que el pack - no trae, lo registra — declara el nombre y su definición, una vez — y luego lo - nombra. + A veces un componente necesita apartarse del sonido por defecto de su familia. Lo hace nombrando otro, en una línea:

{`// 1 · declarar el nombre  ·  2 · definirlo (síntesis, wav o mp3)  ·  3 · nombrarlo
-declare module '$uix/sema' { interface SemaSoundNames { laser: true } }
-createActiveUix({ events: { sounds: { laser: sample('/sounds/laser.mp3', reserva) } } })
-{ selector: onProvider({ eventName: 'contact-activate' }), sound: 'laser' }`}{`// El botón de este componente no hace 'touch', hace 'snap'
+{ selector: onTrigger({ eventName: 'contact-activate' }), sound: 'snap' }`}

- Un sound pack es una implementación completa del vocabulario: mezcla libre de - síntesis, .wav y .mp3. El pack por defecto del framework es 100 % - síntesis a propósito — funciona sin red y no puede dar 404. + Lo que nunca hace —ni él, ni un tema, ni la aplicación— es escribir un + parámetro. No existe «súbele el tono 200 Hz». El tipo acepta un nombre del catálogo o + SILENT, y nada más. Si hace falta un sonido que no está, se + añade al catálogo con su nombre y luego se nombra. +

+

+ Por eso la mayoría de componentes del framework no tienen ninguna regla de sonido: el sistema + ya dice lo correcto, y sólo se escribe una línea cuando hay una decisión de verdad que tomar.

+ +
+

Y a partir de aquí

+
    +
  • + El catálogo — cada sonido que existe, + por qué suena así, y audible. +
  • +
  • + Sound packs — traer tu propia voz: + síntesis, .wav, .mp3, y cómo registrar nombres nuevos. +
  • +
  • + Gestos — por qué un arrastre suena por repetición + y no calculando. +
  • +
+
diff --git a/web/routes/uix/docs/sound/catalogo/+page.svelte b/web/routes/uix/docs/sound/catalogo/+page.svelte new file mode 100644 index 000000000..301231d95 --- /dev/null +++ b/web/routes/uix/docs/sound/catalogo/+page.svelte @@ -0,0 +1,213 @@ + + +
+
Sema · el sonido
+

Todos los sonidos que existen.

+

+ Estos son los sonidos que un componente puede pedir por su nombre. No hay más, y esa es la + gracia: un conjunto pequeño y cerrado significa que puedes aprendértelo, y que dos partes + distintas de la aplicación no acaban sonando parecido por accidente. Púlsalos para oírlos + (necesitas Sound encendido en la barra superior). +

+
+ sonidos{NAMES.length} + ficheros de audioninguno +
+ + + +
+

Dos clases de entrada

+

+ Las bases son el sonido de un tipo de suceso: tocar, confirmar, abrir. Las + versiones + llevan un sufijo con un intent (tick.threat) y son lo que suena cuando ese suceso + ocurre con esa carga. Si un suceso tiene un intent para el que no hay versión, suena la base — + nunca falla, sólo dice menos. +

+
+ +
+

Bases

+ {#each NAMES.filter((n) => !n.includes('.')) as name} + {@const s = SEMA_MAP.sounds[name] as Record} + {@const why = WHY[name]} +
+
+ + {why?.role ?? ''} +
+

{why?.why ?? ''}

+
+ {#each AXES as axis} + {axis}{String(s[axis])} + {/each} +
+
+ {/each} +
+ +
+

Versiones por intent

+ {#each NAMES.filter((n) => n.includes('.')) as name} + {@const s = SEMA_MAP.sounds[name] as Record} + {@const why = WHY[name]} +
+
+ + {why?.role ?? ''} +
+

{why?.why ?? ''}

+
+ {#each AXES as axis} + {axis}{String(s[axis])} + {/each} +
+
+ {/each} +
+ +
+

Qué significan esos números

+

+ Cada sonido se sintetiza a partir de seis valores. Los ves aquí para entender el diseño, pero no vas a escribirlos nunca: viven en el catálogo y en ningún otro sitio. +

+
    +
  • pitch — la altura, en hercios. Grave abajo, agudo arriba.
  • +
  • + centroid — el brillo. Técnicamente es dónde corta un filtro: cuanto + más alto, más armónicos pasan y más «cristalino» suena. +
  • +
  • duration — cuánto dura, en milisegundos.
  • +
  • + contour — hacia dónde va la altura mientras suena: plano, subiendo, bajando o en + arco. +
  • +
  • + roughness — la aspereza. A partir de 0,2 el sonido empieza a «raspar», que es lo + que distingue un aviso de una confirmación. +
  • +
  • gain — el volumen.
  • +
+
+ +
+

Por qué son tan distintos entre sí

+

+ No es casualidad: hay una prueba automática que rechaza el catálogo si dos + sonidos se parecen demasiado. Para convivir, dos entradas tienen que separarse en al menos + dos de esos ejes con margen suficiente para oírlo — una quinta de altura, un tercio + de brillo, la mitad otra vez de duración, otro contorno, el doble de volumen o 0,2 de aspereza. +

+

+ Suena excesivo hasta que ocurre lo contrario. Un conjunto de sonidos que sólo se diferencian + en el volumen no es un vocabulario: es el mismo sonido repetido, y quien usa la aplicación no + distingue nada. +

+
+
diff --git a/web/routes/uix/docs/sound/gestos/+page.svelte b/web/routes/uix/docs/sound/gestos/+page.svelte new file mode 100644 index 000000000..e0b6cff14 --- /dev/null +++ b/web/routes/uix/docs/sound/gestos/+page.svelte @@ -0,0 +1,114 @@ + + +
+
Sema · el sonido
+

Los gestos suenan como una rueda dentada.

+

+ Arrastrar un deslizador o un panel no es un suceso: es muchos, uno detrás de + otro mientras mueves el dedo. Eso cambia por completo cómo debe sonar, y la respuesta del + sistema es la misma que la de una rueda física: un pequeño clic por paso. +

+ + + +
+

El problema de un gesto

+

+ Un botón suena una vez y ya está. Un arrastre, en cambio, dura: mientras mueves, el componente + va contando lo que pasa, varias veces por segundo. Si cada uno de esos avisos sonara como un + botón, tendrías una ametralladora en el oído. +

+

+ La tentación es calcular: hacer que el tono suba con la posición y el volumen con la + velocidad. Suena razonable escrito, pero produce un zumbido que se desliza y del que es + difícil sacar información — y obliga a meter matemáticas dentro de un componente, que es justo + lo que este sistema evita en todas partes. +

+
+ +
+

La respuesta: un clic diminuto, repetido

+

+ El componente ya emite a su propio ritmo mientras arrastras. Basta con que cada emisión toque + un sonido muy corto —step, 18 ms— y la información sale sola: + cuanto más rápido mueves, más rápido suena. La dinámica del gesto es su + ritmo, no su volumen. +

+

+ Es exactamente lo que hace la rueda de un ratón o el selector de un móvil, y funciona por el + mismo motivo: el oído es muy bueno midiendo cadencia. +

+ +
+ + +
+

+ Prueba las tres velocidades seguidas: es el mismo sonido las tres veces, y aun así reconoces + cuál es rápido. +

+
+ +
+

Una excepción que hay que conocer

+

+ El sistema baja el volumen de un sonido que se repite mucho seguido: es una defensa contra la + fatiga, para que veinte notificaciones iguales no te martilleen. +

+

+ Con los gestos eso sería un desastre — la repetición es el mensaje, y esa defensa + apagaría la rueda en el primer arrastre. Por eso la familia + handle está exenta: sus emisiones se supone que son muchas y + regulares, y no significan insistencia. +

+
+ +
+

Qué se gana y qué se pierde

+
    +
  • + Se gana que un componente no calcule nada: nombra + step y ya está, igual que cualquier otro sonido del sistema. +
  • +
  • + Se gana que funcione con ficheros: si tu pack pone un + .mp3 en step, la rueda sigue funcionando igual, cosa que con tono + calculado era imposible. +
  • +
  • + Se pierde la relación entre posición y altura: antes, arrastrar hacia el + final subía el tono. Ahora la velocidad se oye en el ritmo, pero + dónde estás no se oye. Es una decisión consciente; si algún día hace falta, se recupera + con pasos cuantizados y no con matemáticas dentro del componente. +
  • +
+
+
diff --git a/web/routes/uix/docs/sound/packs/+page.svelte b/web/routes/uix/docs/sound/packs/+page.svelte new file mode 100644 index 000000000..1c9dfac06 --- /dev/null +++ b/web/routes/uix/docs/sound/packs/+page.svelte @@ -0,0 +1,164 @@ + + +
+
Sema · el sonido
+

Traer tus propios sonidos.

+

+ Los componentes piden sonidos por su nombre: tick, open, + alert. Lo que suena cada nombre lo decide una pieza aparte, un + sound pack. Cambiando el pack cambia la voz de toda la aplicación — sin tocar + un solo componente. +

+ +
+

La idea: nombres por un lado, sonidos por otro

+

+ Piénsalo como una lista de palabras y un diccionario. La lista de palabras es fija: son los {COUNT} + nombres que un componente puede pronunciar, y no se inventan sobre la marcha. El diccionario —el + pack— dice a qué suena cada palabra, y ése sí lo puedes cambiar entero. +

+
{`// Lo que dice un componente. Nunca cambia.
+sound: 'tick'
+
+// Lo que dice el pack. Puedes reemplazarlo.
+tick: { pitch: 850, centroid: 3200, duration: 70, contour: 'flat', gain: 0.08 }
+tick: sample('/sonidos/mi-tick.mp3', reservaSintetica)   // ← o esto`}
+

+ Al componente le da igual cuál de las dos sea. Esa indiferencia es justo lo que + hace posible cambiar la voz sin tocar nada más. +

+
+ +
+

El pack que viene de serie no trae ficheros

+

+ Los {COUNT} sonidos por defecto están sintetizados: se generan en el + navegador con osciladores. Eso significa que suenan sin descargar nada, sin esperas la primera + vez, y sin que un despliegue mal copiado deje la aplicación muda. +

+

+ Si prefieres ficheros, los pones tú. Los .wav que hay en el repositorio son sólo un + ejemplo de cómo se hace. +

+
+ +
+

Usar ficheros de audio

+
{`sample('/sonidos/tick.mp3', {
+	// La reserva: lo que suena si el fichero no se puede cargar o decodificar.
+	// Ponla siempre — sin ella, un 404 deja ese sonido mudo para siempre.
+	pitch: 850, centroid: 3200, roughness: 0, attack: 2,
+	decay: 60, duration: 70, contour: 'flat', gain: 0.08
+})`}
+

+ Valen wav, mp3, ogg y aac — el navegador los decodifica todos. Puedes mezclar libremente: + unos nombres sintetizados y otros con fichero, en el mismo pack. +

+

+ Un límite que conviene conocer antes de diseñar. De un fichero sólo se puede + controlar el volumen: no se le puede cambiar el tono ni la velocidad. Así que si quieres que + un mismo suceso suene distinto según su carga —una confirmación normal frente a una peligrosa— + necesitas un fichero por cada caso + (tick.mp3 y tick.threat.mp3), no uno solo modificado. Con sonidos + sintetizados el problema no existe, porque cada versión se diseña entera. +

+
+ +
+

Añadir un sonido que no está en la lista

+

+ La lista de nombres está cerrada para que nadie improvise, pero no está cerrada a que crezca. Si tu producto necesita un sonido propio, lo registras una vez y a partir de ahí se nombra + como cualquier otro. +

+
{`// 1 · Declara que el nombre existe (una vez, en tu producto)
+declare module '$uix/sema' {
+	interface SemaSoundNames { laser: true }
+}
+
+// 2 · Di a qué suena
+createActiveUix({
+	events: { sounds: { laser: sample('/sonidos/laser.mp3', reserva) } }
+})
+
+// 3 · Úsalo donde quieras
+{ selector: onProvider({ eventName: 'contact-activate' }), sound: 'laser' }`}
+

+ Si además registras 'laser.threat', la regla normal lo encontrará sola cuando el + suceso lleve ese intent. Y como el nombre está declarado en los tipos, una errata ('lasser') no compila. +

+
+ +
+

Cambiar la voz en caliente

+

+ Un pack no es sólo algo que se elige al arrancar: se puede cambiar mientras la aplicación + corre, y deshacer. +

+
{`// El sonido es un eje del tema, junto al color y los demás
+eidos.applyTheme({ color: '#3b5bdb', sound: { tick: { gain: 0.05 } } });
+eidos.clearTheme();                        // vuelve a lo de origen
+
+// O sólo el sonido
+uix.events.applySounds({ tick: { gain: 0.05 } });
+uix.events.clearMap();`}
+

+ Fíjate en qué se puede cambiar y qué no: puedes cambiar a qué suena un nombre, pero + no qué nombres existen. Un tema ajusta la voz; no puede inventar una palabra que + ningún componente sabría pronunciar. +

+
+ +
+

Quién puede hacer qué

+
+ + + + + + + + + + + + + + + + + + + + + +
si eres…puedescómo
el productoañadir nombres nuevosdeclararlo + registrar su sonido
un temacambiar a qué suenanapplySounds()
un componenteelegir cuál usasound: 'tick' o SILENT
+
+

+ Y algo que nadie puede, en ninguna de las tres posiciones: escribir un parámetro + suelto. No hay forma de decir «este botón, 200 Hz más agudo». Los números viven en el pack, y sólo + ahí. +

+
+
diff --git a/web/routes/uix/uix.css b/web/routes/uix/uix.css index 19105dc66..3e69413a8 100644 --- a/web/routes/uix/uix.css +++ b/web/routes/uix/uix.css @@ -1889,3 +1889,49 @@ samp { [data-uix-prose-list] li { margin-block-end: var(--uix-space-2); } + +/* ── /uix/docs/sound/catalogo — one entry: chip, role, reason, measurements ── */ + +[data-uix-sound-entry] { + padding-block: var(--uix-space-5); + border-block-end: 1px solid var(--uix-line-soft); +} + +[data-uix-sound-entry-head] { + display: flex; + align-items: center; + gap: var(--uix-space-4); + flex-wrap: wrap; +} + +[data-uix-sound-entry-head] [data-uix-sound-chip] { + min-width: 10rem; + flex: 0 0 auto; +} + +[data-uix-sound-entry-role] { + font-size: var(--uix-text-md); + color: var(--uix-text); + font-weight: 500; +} + +[data-uix-sound-entry-why] { + margin-block: var(--uix-space-3) var(--uix-space-4); + max-width: 62ch; + line-height: var(--uix-leading-prose); + color: var(--uix-text-muted); +} + +[data-uix-sound-axes] { + display: flex; + flex-wrap: wrap; + gap: var(--uix-space-2) var(--uix-space-4); + font-family: var(--uix-font-mono); + font-size: var(--uix-text-2xs); + color: var(--uix-text-faint); +} + +[data-uix-sound-axis-key] { + margin-inline-end: var(--uix-space-1); + opacity: 0.7; +}