blocks(logo-cloud): la banda de marcas vuelve, y su parte difícil no era la disposición

Se difirió en F2 sobre una premisa que las referencias desmienten —que su versión
honesta pedía un marquee o quedaba en un wrap trivial—: Tailwind Plus, Untitled y
Flowbite lo shippean estático. Una decisión cuya premisa cae se corrige.

Pero el argumento para construirlo no es la paridad. Lo difícil de un muro de
logos es la tinta: veinte marcas en veinte paletas gritan sobre la página y entre
ellas. Ese tratamiento acaba de entrar en la fundación como contexto, así que el
bloque queda fino y es exactamente la forma correcta — cuando lo difícil es un
tratamiento, el tratamiento va a la fundación y el bloque sólo lo estampa. Aquí no
hay una línea de CSS, que es además lo que el contrato exige.

Dos decisiones medidas, no elegidas. Las marcas van en un wrap y no en una
rejilla, porque llegan con proporciones dispares y unas pistas iguales dejan
huecos irregulares alrededor de las estrechas. Y la parte del ítem se gana su
sitio defaulteando el eje de bloque, que es lo único que un logo nunca trae
consigo: un vectorial carga su viewBox, un raster sus píxeles, y uno al lado del
otro aterrizan a cinco alturas distintas.

Verificado con ocho marcas de anchos y colores deliberadamente dispares: las ocho
a una sola altura conservando sesenta y cuatro píxeles de diferencia de ancho, que
es cada una guardando su proporción.

El marquee se queda como hueco con disparador y con su forma decidida: si algún
día entra será un preset de movimiento sobre la fila, no un bloque nuevo, y con la
preferencia de movimiento reducido parándolo.

Y el hueco de la utilidad para nombres sólo audibles sale aquí por tercera vez en
la ola. Tres consumidores reales lo piden ya.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent ad26922d29
commit b7474e499d

@ -260,7 +260,7 @@ single-price~~ ✅ · ~~V5 cta split~~ ✅ · ~~V2 testimonials spotlight~~ ✅
2026-08-17; entra por el tramo B**) · **B** ✅ **CERRADO 2026-08-17** (15 matrices + guard encendido y probado en rojo) — la matriz de
equivalencia variante-a-variante en el README de cada block, con recetas
demostradas en demo (el entregable central: responde a la brecha de
cardinalidad sin competir en dumps) · **C** tres blocks nuevos — `logo-cloud`
cardinalidad sin competir en dumps) · **C** (~~`logo-cloud`~~ ✅ **HECHO 2026-08-18**, con el eje `data-ink` en la fundación) **C** tres blocks nuevos — `logo-cloud`
(reabierto: las refs lo shippean estático), `blog`, `cookie-consent` (fase 0
legal profunda; la ley vive en el TIPO) — y `onboarding` NO entra (receta de
`wizard`) · **D** la página compuesta como puerta de cierre.

@ -2373,3 +2373,31 @@ type="button">` es una pista de MIME falsa) — el morfo lo declara
antiguos actualizados (el self-test los cazó él solo al cambiar la regla) y
**probado en rojo sobre el árbol real**: quitándole la sección a `team` sale su
línea exacta, y restaurado vuelve a verde.
- 2026-08-18 — **F2b · C · N1 `logo-cloud` HECHO, y el eje de tinta con él**. El
block se difirió en F2 sobre una premisa que las referencias desmienten («su
versión honesta pide `Marquee`»): TW (6), Untitled y Flowbite lo shippean
ESTÁTICO. Reabierto — pero el argumento para construirlo no es la paridad: **la
parte difícil de un muro de logos es la TINTA, no la disposición**.
**Ese tratamiento fue al CANON, a la fundación, no al block ni a `Image`.**
`data-ink` es el hermano de `data-on`: uno re-entinta porque cambió el LIENZO,
el otro porque el CONTENIDO son marcas ajenas. Medido antes de elegir sitio:
`Image` **no tiene eje de tinta** (ni prop ni regla) y tampoco era su casa — es
un componente con máquina de carga para fotos, mientras que un logo suele ser un
`<svg>` en línea, y el mecanismo es DOBLE (`currentColor` para vectorial,
`filter` para raster). Un primitivo `Logo` nuevo se descartó por lo mismo: no
habría cargado más que el contexto. **Cero tokens nuevos** (`--opacity-muted` y
`--opacity-full` ya estaban en el tier semántico de la escala). Detalle y
medidas: `theming/changelog.md` §52.
**El block queda FINO**, que es la forma correcta cuando lo difícil es un
tratamiento: estampa `data-ink` en su sección y coloca un `Wrap`. `.Items` es
`Wrap` y **no rejilla** —medido: proporciones dispares dejan huecos irregulares
en pistas iguales— y `.Item` se gana la parte **DEFAULTEANDO** el eje de bloque,
lo único que un logo nunca trae consigo. Verificado con 8 marcas de anchos y
colores distintos: `mono` → gris al 0.65 con la tinta del tema (y otra en
oscuro), `brand` → su color, puntero → color entero; **una sola altura (28px)
con 64px de diferencia de ancho**, que es cada marca guardando su proporción.
⚠️ **Dos lecciones**: (1) `renderBlock` del generador **no añade `;`** entre
declaraciones —cada una trae el suyo salvo la última—, y sin ellos las reglas
LLEGAN a la hoja y no pintan nada; anotado en el propio generador. (2) El hueco
**`visually-hidden`** sale aquí por TERCERA vez en la ola (`newsletter`,
`site-footer`, `logo-cloud`): es candidato de canon con tres consumidores reales.

@ -0,0 +1,106 @@
# LogoCloud
## Function
The social-proof band: the marks of the customers who already use the product.
It sits under a hero or above a footer and answers one question — «who else
trusts this» — with evidence rather than adjectives.
## Composition map
| Part | Composes | Notes |
| --------- | ---------------------------------------------------- | -------------------------------------------------------------------------------- |
| root | bare `<section>` + `Section` + `Container` + `Stack` | stamps `data-ink` — the treatment belongs to the SET, not to any one mark |
| entrance | `Motion` (`trigger="viewport"`) | ONE. A band of logos is a single statement, like the `cta` panel |
| `eyebrow` | — (app: `Text`) | the «Trusted by…» line; the app owns the words (B-7) |
| `title` | `Heading` (level 2) | optional. With it the section is a named landmark; without it, a decorative band |
| `.Items` | `Wrap` | the flow line — NOT a grid, see Decisions |
| `.Item` | `Box` | normalises the block-axis size and leaves the inline size free |
**Landmark + headings**: with a `title`, one `<section>` named by it through
`aria-labelledby`, and the title is an `<h2>`. Without a title the section has no
accessible name and is **not** an exposed landmark — which is correct for a
decorative band, but the marks themselves still need names: an `alt` on the image
or `role="img"` + `aria-label` on the `<svg>`. The block does not invent them.
## Coordination
_Position under the 2026-07-31 doctrine ([`architecture/blocks.md`](../../../../docs/architecture/blocks.md) §«Coordination»)._
**Owns nothing.** A band of marks has no state that can block anything and no
words of its own. It is a layout block, like `feature-grid`: giving it a machine
would be the opposite mistake.
## Decisions
**2026-08-17 — reopened, and why** (plan F2b, stretch C). This block was deferred
at F2 on a premise the references disprove: «its honest version needs a `Marquee`
or it is a trivial `Wrap`». Tailwind Plus (6), Untitled and Flowbite all ship it
**static**. A decision whose premise falls gets corrected — but the argument to
build it is not parity, it is this:
- **The hard part of a logo wall is the INK, not the layout.** Twenty marks in
twenty palettes shout over the page and each other. Every reference solves it by
hand-tuning the assets, which is also why they sell dark mode as a second
artefact: a hand-tuned asset cannot follow a theme.
- **So the treatment went to the FOUNDATION, not here and not to `Image`.** It is
`data-ink`, a subtree context — the sibling of `data-on`. `data-on` re-inks
because the CANVAS changed; `data-ink` re-inks because the CONTENT is other
people's brands. Measured before choosing: `Image` has no ink axis at all (no
prop, no rule in its recipe), and it would have been the wrong home anyway —
`Image` is a component with a load-state machine for photos, while a logo is
usually an inline `<svg>`, and the mechanism is DOUBLE (an `<svg>` takes ink
through `currentColor`, a raster through `filter`). One axis has to cover both.
A new `Logo` primitive was rejected for the same reason it would have carried
nothing but the context.
- **Zero new tokens.** Rest and hover are `--opacity-muted` (0.65) and
`--opacity-full`, which the semantic tier of the opacity scale already had.
- **Per-mark opt-out**: `data-ink="brand"` on a single mark wins by order at equal
specificity. Some trademarks may not be altered and the block cannot know which,
so the consumer says so on the mark itself.
- **`.Items` is a `Wrap`, not an `AutoGrid`** — measured, not stylistic: logos
arrive in wildly different aspect ratios, so equal tracks leave ragged holes
around the narrow ones and the wall reads as a broken table.
- **`.Item` earns its place by DEFAULTING**: it normalises the block-axis size,
the one thing a logo never brings with it (an SVG carries its viewBox, a raster
its pixels, and side by side they land at five different heights).
## Demo
`web/routes/blocks/logo-cloud/` — eight marks of deliberately different widths and
colours, so the normalisation and the ink treatment can be seen; live control of
`ink` and of the title. Mini-page in `LogoCloudSite.svelte`, shared by both
surfaces.
## Equivalencias
_La matriz del tramo B: cada variante que las referencias shippean, frente a la
composición nuestra que la logra. **Una receta que no se ha visto en el navegador
NO entra en esta tabla**._
| Variante de la referencia | Refs | Receta | Demostrada en | Estado |
| -------------------------------------- | ---------------------------- | -------------------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Banda de marcas estática | TW (6) · Untitled · Flowbite | `.Items` + `.Item` | por defecto | cubierta |
| Marcas apagadas que reviven al puntero | TW · Untitled | `ink="mono"` (contexto `data-ink` de la fundación) | control `tinta` | **superación**: las refs lo consiguen retocando cada asset, así que su dark mode es un segundo artefacto; aquí sale del tema |
| Marcas en su color | Flowbite | `ink="brand"` | control `tinta` | cubierta |
| Una marca exenta del tratamiento | — **ninguna** | `data-ink="brand"` en esa marca | — | gap: `logo-cloud-optout-demo` (la receta funciona por cascada; falta enseñarla) |
| Línea «Trusted by…» | todas | slot `eyebrow` | por defecto | cubierta |
| Con título de sección | TW · Untitled | slot `title` (y con él, landmark nombrado) | control `título` | cubierta |
| Marcas en marcha (marquee) | Flowbite · Relume | — | — | gap: `marquee` (no existe en el canon; si entra será un preset de motion sobre `.Items`, no un block nuevo, y con `prefers-reduced-motion` parándolo) |
| Logo decorativo + nombre sólo para AT | TW | — | — | gap: `visually-hidden` (candidato de CANON: no existe el primitivo. **Tercera vez que este hueco sale en la ola**) |
## Gaps
| Gap | Disposition |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`Marquee`** | **gap with a trigger** — it does not exist in the canon. If it ever lands it is a motion preset over `.Items`, not a new block, and `prefers-reduced-motion` stops it. This was the premise the block was deferred on, and the references disprove it |
| **`visually-hidden`** | **canon** — the primitive does not exist, so the references' «decorative mark + name for AT only» row cannot be reproduced without inventing. Named three times in this wave (`newsletter`, `site-footer`, here) |
| **Brand assets** | **app-land** — no trademarks in the framework. The demo composes wordmarks of invented names |
## Found while composing
- **The ink context is the block's whole reason to exist, and it is NOT the
block's code.** Everything this file does is stamp `data-ink` on its section and
place a `Wrap`. That is the shape the tier wants: when the hard part is a
treatment, the treatment goes to the foundation and the block stays thin — the
opposite of pulling the CSS into the block, which D-BLK.2 forbids anyway.

@ -0,0 +1,35 @@
// LogoCloud — the social-proof band of customer marks.
//
// import { LogoCloud } from '$blocks/logo-cloud';
//
// <LogoCloud>
// {#snippet eyebrow()}<Text size="sm" color="muted">Con la confianza de</Text>{/snippet}
// <LogoCloud.Items>
// <LogoCloud.Item><img src="/acme.svg" alt="Acme" /></LogoCloud.Item>
// </LogoCloud.Items>
// </LogoCloud>
//
// The ink treatment is the foundation's `data-ink` context, stamped on the
// section: a wall of other people's brands is a problem of the SET.
import LogoCloudComponent from './logo-cloud.svelte';
import Items from './logo-cloud-items.svelte';
import Item from './logo-cloud-item.svelte';
type LogoCloudNamespace = typeof LogoCloudComponent & {
Items: typeof Items;
Item: typeof Item;
};
const LogoCloud = LogoCloudComponent as LogoCloudNamespace;
LogoCloud.Items = Items;
LogoCloud.Item = Item;
export { LogoCloud };
export default LogoCloud;
export type {
LogoCloudProps,
LogoCloudInk,
LogoCloudItemsProps as ItemsProps,
LogoCloudItemProps as ItemProps
} from './types';

@ -0,0 +1,22 @@
<script lang="ts">
/**
* LogoCloud.Item — one mark.
*
* It earns its place by DEFAULTING, which is the tier's other rule for a part:
* it normalises the block-axis size, the thing a logo never brings with it (an
* SVG carries its own viewBox, a raster its own pixels, and side by side they
* come out at five different heights). The inline size stays free so each mark
* keeps its proportion.
*
* The ink is NOT here: it cascades from the section's `data-ink`, so a consumer
* can opt one single mark out with `data-ink="brand"` on its own content.
*/
import { Box } from '$uix/eidos/components/box';
import type { LogoCloudItemProps } from './types';
let { height = '1.75rem', children, ...rest }: LogoCloudItemProps = $props();
</script>
<Box {height} {...rest}>
{@render children?.()}
</Box>

@ -0,0 +1,18 @@
<script lang="ts">
/**
* LogoCloud.Items — the flow line the marks lay out in.
*
* A `Wrap` and NOT an `AutoGrid`, measured rather than chosen: logos arrive in
* wildly different aspect ratios, so equal tracks leave ragged holes around the
* narrow ones and the wall reads as a broken table. A flow line centres what
* fits and wraps the rest.
*/
import { Wrap } from '$uix/eidos/components/wrap';
import type { LogoCloudItemsProps } from './types';
let { gap = 10, children, ...rest }: LogoCloudItemsProps = $props();
</script>
<Wrap {gap} align="center" justify="center" width="100%" {...rest}>
{@render children?.()}
</Wrap>

@ -0,0 +1,60 @@
<script lang="ts">
/**
* LogoCloud — the social-proof band: the marks of the customers who already
* use the product.
*
* It was deferred at F2 on a premise the references disprove («its honest
* version needs a Marquee»): Tailwind Plus, Untitled and Flowbite all ship it
* STATIC. Reopened in the wave's stretch C, and with a reason of its own — the
* hard part of a logo wall is not the layout, it is the INK. That treatment is
* the foundation's `data-ink` context, so the block only has to stamp it.
*
* B contract: canon components only — no `.css`, no morfo, no strings.
*/
import { Section } from '$uix/eidos/components/section';
import { Container } from '$uix/eidos/components/container';
import { Stack } from '$uix/eidos/components/stack';
import { Heading } from '$uix/eidos/components/heading';
import { Motion } from '$uix/eidos/components/motion';
import type { LogoCloudProps } from './types';
let {
ink = 'mono',
containerSize = 'lg',
sectionSize = 'md',
eyebrow,
title,
children,
...rest
}: LogoCloudProps = $props();
const uid = $props.id();
const titleId = `${uid}-title`;
</script>
<!--
`data-ink` on the SECTION, not on each mark: the treatment belongs to the set.
Twenty marks in twenty palettes are a problem of the wall, not of any one logo,
which is also why this is a context and not a prop on `Image`.
-->
<section aria-labelledby={title ? titleId : undefined} data-ink={ink} {...rest}>
<Section size={sectionSize}>
<Container size={containerSize}>
<!-- One `Motion`: a band of logos is a single statement, like the `cta`
panel. Dealing marks in one by one would turn evidence into theatre. -->
<Motion trigger="viewport">
<Stack gap={6} align="center">
{#if eyebrow}
{@render eyebrow()}
{/if}
{#if title}
<Heading id={titleId} level={2} size="sm" align="center">
{@render title()}
</Heading>
{/if}
{@render children?.()}
</Stack>
</Motion>
</Container>
</Section>
</section>

@ -0,0 +1,64 @@
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import type { ContainerSize } from '$uix/eidos/components/container';
import type { SectionSize } from '$uix/eidos/components/section';
import type { WrapProps } from '$uix/eidos/components/wrap';
import type { BoxProps } from '$uix/eidos/components/box';
/**
* How the marks are inked.
*
* `mono` is the whole reason this block exists. A wall of customer logos is the
* one section where the CONTENT is other people's brands: twenty marks in twenty
* palettes shout over the page and each other. Every reference catalog solves it
* by hand-tuning the assets — and sells dark mode as a second artefact, because
* a hand-tuned asset cannot follow a theme.
*
* Here it is the foundation's `data-ink` context doing the work: the marks take
* the theme's muted ink at rest and their own colour under the pointer. Derived,
* so a palette or mode swap carries them along.
*
* `brand` leaves every mark as it came. A consumer can also opt a SINGLE mark out
* with `data-ink="brand"` on it — some trademarks may not be altered and the
* block cannot know which.
*/
export type LogoCloudInk = 'mono' | 'brand';
export type LogoCloudProps = Omit<HTMLAttributes<HTMLElement>, 'children' | 'title'> & {
/**
* How the marks are inked. @default 'mono'
*/
ink?: LogoCloudInk;
/** Content measure. Passed straight to `Container`. @default 'lg' */
containerSize?: ContainerSize;
/** Block-axis padding. Passed straight to `Section`. @default 'md' */
sectionSize?: SectionSize;
/** A small line above the marks («Trusted by…»). The app owns the words (B-7). */
eyebrow?: Snippet;
/**
* The section's title, wrapped by the block in a `Heading` so it owns the
* landmark `id`. Without it the band has no accessible name and is not an
* exposed landmark — see the README.
*/
title?: Snippet;
/** The `LogoCloud.Item`s. */
children?: Snippet;
};
/**
* Wraps a `Wrap`, not a grid — and that is measured, not stylistic: logos come in
* wildly different aspect ratios, so equal tracks leave ragged holes around the
* narrow ones. A flow line centres them and closes the gaps.
*/
export type LogoCloudItemsProps = Omit<WrapProps, 'children'> & {
children?: Snippet;
};
/**
* One mark. The part exists because it REPEATS and because it DEFAULTS: it
* normalises the block-axis size, which is the thing a logo never brings with it.
* The inline size stays free so each mark keeps its own proportion.
*/
export type LogoCloudItemProps = Omit<BoxProps, 'children'> & {
children?: Snippet;
};

@ -27,6 +27,7 @@ export const BLOCK_CATALOG: BlockGroup[] = [
{ slug: 'testimonials', label: 'Testimonials', shipped: true },
{ slug: 'faq', label: 'FAQ', shipped: true },
{ slug: 'stats-band', label: 'Stats band', shipped: true },
{ slug: 'logo-cloud', label: 'Logo cloud', shipped: true },
{ slug: 'cta', label: 'CTA', shipped: true },
{ slug: 'newsletter', label: 'Newsletter', shipped: true },
{ slug: 'site-footer', label: 'Site footer', shipped: true },

@ -0,0 +1,169 @@
<script lang="ts">
/**
* LogoCloud demo. Full-bleed on the page; device widths from `./preview`.
*/
import { Stack } from '$uix/eidos/components/stack';
import { Group } from '$uix/eidos/components/group';
import { Wrap } from '$uix/eidos/components/wrap';
import { Text } from '$uix/eidos/components/text';
import { Code } from '$uix/eidos/components/code';
import { ToggleGroup } from '$uix/eidos/components/toggle-group';
import BlockDemo from '../_lib/BlockDemo.svelte';
import LogoCloudSite from './LogoCloudSite.svelte';
import DocRow from '../_lib/DocRow.svelte';
let ink = $state<'mono' | 'brand'>('mono');
let withTitle = $state(false);
const previewSrc = $derived(`/blocks/logo-cloud/preview?ink=${ink}&title=${withTitle}`);
</script>
<BlockDemo
name="LogoCloud"
slug="$blocks/logo-cloud"
{previewSrc}
previewHeight="520px"
meta={[
{ key: 'compone', value: 'Section · Container · Stack · Wrap · Box · Heading · Motion' },
{ key: 'landmark', value: 'section aria-labelledby (con título)' },
{ key: 'tipo', value: 'compound (.Item repite y DEFAULTEA)' },
{ key: 'strings propias', value: '0' }
]}
>
{#snippet preview()}
<LogoCloudSite {ink} {withTitle} />
{/snippet}
{#snippet controls()}
<Wrap gap={5}>
<Group gap={2} align="center" justify="start">
<Text size="sm" color="muted">tinta</Text>
<ToggleGroup
selectionMode="single"
size="sm"
attached
value={[ink]}
onValueChange={(v) => (ink = (v[0] ?? ink) as typeof ink)}
aria-label="tratamiento de tinta de las marcas"
>
<ToggleGroup.Item value="mono">mono</ToggleGroup.Item>
<ToggleGroup.Item value="brand">brand</ToggleGroup.Item>
</ToggleGroup>
</Group>
<Group gap={2} align="center" justify="start">
<Text size="sm" color="muted">título</Text>
<ToggleGroup
selectionMode="single"
size="sm"
attached
value={[withTitle ? 'sí' : 'no']}
onValueChange={(v) => (withTitle = (v[0] ?? 'no') === 'sí')}
aria-label="título de la sección"
>
<ToggleGroup.Item value="sí">sí</ToggleGroup.Item>
<ToggleGroup.Item value="no">no</ToggleGroup.Item>
</ToggleGroup>
</Group>
</Wrap>
{/snippet}
{#snippet lede()}
La banda de prueba social: las marcas de quien ya usa el producto. Su parte difícil no es la
disposición, es la <strong>tinta</strong> — veinte marcas en veinte paletas se gritan entre sí.
Prueba el control <em>tinta</em>: en <Code>mono</Code> las marcas toman la tinta apagada del tema
y recuperan su color bajo el puntero; en <Code>brand</Code> se quedan como llegaron.
{/snippet}
{#snippet composition()}
<Stack gap={4}>
<Text color="muted">
El block coloca y estampa el contexto de tinta; las marcas son de la app.
</Text>
<Stack gap={3}>
<DocRow term="la tinta">
<Code>data-ink</Code> de la FUNDACIÓN, estampado en la sección — no una prop de
<Code>Image</Code> ni un primitivo nuevo. Un muro de marcas ajenas es un problema del CONJUNTO,
y el mecanismo es doble: un <Code>svg</Code> en línea toma la tinta por
<Code>currentColor</Code> y un raster por <Code>filter</Code>. Un solo eje cubre los dos.
</DocRow>
<DocRow term=".Items">
Un <Code>Wrap</Code>, no una rejilla: los logos llegan con proporciones dispares y unas
pistas iguales dejan huecos irregulares alrededor de los estrechos. La línea fluida centra
lo que cabe y envuelve el resto.
</DocRow>
<DocRow term=".Item">
Normaliza el eje de bloque —lo que una marca nunca trae consigo— y deja el ancho libre
para que cada una conserve su proporción.
</DocRow>
<DocRow term="entrada">
Un solo <Code>Motion</Code>: una banda de logos es una sola afirmación, como el panel del
<Code>cta</Code>. Repartir las marcas una a una convertiría la evidencia en teatro.
</DocRow>
</Stack>
</Stack>
{/snippet}
{#snippet api()}
<Stack gap={3}>
<DocRow term="LogoCloud">
<Code>ink</Code> (<Code>'mono' | 'brand'</Code>, def. <Code>mono</Code>) ·
<Code>containerSize</Code> (def. <Code>lg</Code>) · <Code>sectionSize</Code> (def.
<Code>md</Code>) · snippets <Code>eyebrow</Code> y <Code>title</Code>.
</DocRow>
<DocRow term=".Items · .Item">
<Code>.Items</Code> reenvía el <Code>gap</Code> del <Code>Wrap</Code> (def. <Code>10</Code
>);
<Code>.Item</Code> el <Code>height</Code> (def. <Code>1.75rem</Code>).
</DocRow>
<DocRow term="excepción por marca">
Una marca concreta se libra con <Code>data-ink="brand"</Code> en su propio contenido: hay marcas
registradas que no se pueden alterar y el block no puede saber cuáles.
</DocRow>
</Stack>
{/snippet}
{#snippet a11y()}
<Stack gap={3}>
<DocRow term="Landmark">
Con <Code>title</Code>, un <Code>section</Code> nombrado por él. Sin título es una banda decorativa
y no una región expuesta — pero las marcas siguen necesitando nombre accesible.
</DocRow>
<DocRow term="Nombre de cada marca">
Es de la app: un <Code>alt</Code> en la imagen o <Code>role="img"</Code> +
<Code>aria-label</Code> en el <Code>svg</Code>. El block no lo inventa.
</DocRow>
<DocRow term="Movimiento">
Ninguno automático. La transición de la tinta se anula bajo
<Code>prefers-reduced-motion</Code>, y bajo <Code>forced-colors</Code> el desaturado se retira
entero: ahí la paleta es del sistema.
</DocRow>
</Stack>
{/snippet}
{#snippet gaps()}
<Stack gap={3}>
<DocRow term="Marquee (marcas en marcha)">
<strong>Gap con disparador</strong> — no existe en el canon. Si entra, será un preset de
motion sobre <Code>.Items</Code>, no un block nuevo, y con
<Code>prefers-reduced-motion</Code> parándolo. Fue la premisa con la que este block se difirió
en F2, y las referencias la desmienten: TW, Untitled y Flowbite lo shippean estático.
</DocRow>
<DocRow term="Nombre visible junto a la marca">
<strong>Gap</strong> — <Code>visually-hidden</Code>: no existe el primitivo, así que la fila
«logo decorativo + nombre sólo para AT» no se reproduce sin inventar. Es la TERCERA vez que
este hueco sale en la ola.
</DocRow>
</Stack>
{/snippet}
{#snippet notes()}
<Stack gap={3}>
<DocRow term="Por qué la tinta no vive en Image">
<Code>Image</Code> es un componente con motor de estados para fotos; un logo suele ser un
<Code>svg</Code> en línea. Y la tinta es propiedad del CONJUNTO: el problema es que veinte marcas
se griten, y eso se resuelve donde viven las veinte.
</DocRow>
</Stack>
{/snippet}
</BlockDemo>

@ -0,0 +1,83 @@
<script lang="ts">
/**
* The logo-cloud mini-page: eight marks of deliberately different proportions
* and colours, so the normalisation and the ink treatment can be SEEN.
*
* The marks are inline `<svg>` wordmarks composed here — no external asset and
* no real trademark: each takes its ink through `currentColor`, which is the
* half of the mechanism a raster cannot show.
*/
import { LogoCloud } from '$blocks/logo-cloud';
import { Section } from '$uix/eidos/components/section';
import { Container } from '$uix/eidos/components/container';
import { Stack } from '$uix/eidos/components/stack';
import { Heading } from '$uix/eidos/components/heading';
import { Text } from '$uix/eidos/components/text';
let {
ink = 'mono' as 'mono' | 'brand',
withTitle = false
}: { ink?: 'mono' | 'brand'; withTitle?: boolean } = $props();
// Anchos y colores dispares A PROPÓSITO: si todas las marcas midieran igual, la
// normalización del `.Item` no se vería y el eje de tinta tampoco.
const marks = [
{ name: 'Northwind', w: 128, color: '#0f766e' },
{ name: 'Umbra', w: 86, color: '#7c3aed' },
{ name: 'Kessel', w: 104, color: '#b45309' },
{ name: 'Volta', w: 72, color: '#be123c' },
{ name: 'Marisol', w: 118, color: '#1d4ed8' },
{ name: 'Cobalt', w: 96, color: '#15803d' },
{ name: 'Nimbus', w: 110, color: '#a21caf' },
{ name: 'Faro', w: 64, color: '#0369a1' }
];
</script>
<LogoCloud {ink}>
{#snippet eyebrow()}
<Text size="sm" weight="medium" color="muted">Con la confianza de equipos de producto en</Text>
{/snippet}
{#snippet title()}
{#if withTitle}Ya miden con nosotros{/if}
{/snippet}
<LogoCloud.Items>
{#each marks as mark (mark.name)}
<LogoCloud.Item>
<!-- Wordmark inline: toma la tinta por `currentColor`, así que bajo
`ink="mono"` sigue al tema y bajo `brand` conserva la suya. El
`<title>` del SVG es su nombre accesible. -->
<svg
viewBox="0 0 {mark.w} 28"
width={mark.w}
height="28"
role="img"
aria-label={mark.name}
style="color: {ink === 'brand' ? mark.color : 'currentColor'}; height: 100%; width: auto;"
>
<text
x="0"
y="20"
fill="currentColor"
font-family="system-ui, sans-serif"
font-size="20"
font-weight="600">{mark.name}</text
>
</svg>
</LogoCloud.Item>
{/each}
</LogoCloud.Items>
</LogoCloud>
<!-- A floor so the page reads as a page. -->
<Section size="lg">
<Container size="md">
<Stack gap={4} align="center">
<Heading level={2} align="center">Del evento crudo a la decisión</Heading>
<Text color="muted" align="center">
Los eventos llegan tal cual y salen en cohortes, sin reprocesar.
</Text>
</Stack>
</Container>
</Section>

@ -0,0 +1,31 @@
<script lang="ts">
/**
* The preview is its OWN page — `@` resets the layout, so the section's topbar
* and rail are not here. The axes come from the URL, so the demo's iframe can
* drive them.
*/
import { page } from '$app/state';
import BootUix, { type BootLanguage } from '../../_lib/BootUix.svelte';
import '@/uix/eidos/index.css';
let { children } = $props();
const mode = $derived(page.url.searchParams.get('mode') === 'dark' ? 'dark' : 'light');
const dir = $derived(page.url.searchParams.get('dir') === 'rtl' ? 'rtl' : 'ltr');
const language = $derived((page.url.searchParams.get('lang') ?? 'es') as BootLanguage);
</script>
<svelte:head>
<meta name="color-scheme" content="light dark" />
</svelte:head>
<BootUix {mode} {dir} {language}>
<div
data-theme={mode}
data-mode={mode}
{dir}
style="min-block-size: 100dvh; background: var(--color-surface-default); color: var(--color-content-primary);"
>
{@render children?.()}
</div>
</BootUix>

@ -0,0 +1,13 @@
<script lang="ts">
/**
* Standalone page for the logo-cloud mini-site — same component the demo
* renders inline, served as its OWN document for the device-width frame.
*/
import { page } from '$app/state';
import LogoCloudSite from '../LogoCloudSite.svelte';
const ink = $derived((page.url.searchParams.get('ink') ?? 'mono') as 'mono' | 'brand');
const withTitle = $derived(page.url.searchParams.get('title') === 'true');
</script>
<LogoCloudSite {ink} {withTitle} />
Loading…
Cancel
Save

Powered by TurnKey Linux.