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
parent
ad26922d29
commit
b7474e499d
@ -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;
|
||||
};
|
||||
@ -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…
Reference in new issue