extraído de attio.com Ver sitio
Siete capas de sombra, y ninguna pasa del 7%

El botón no es azul.
Es negro.

Attio es un CRM y su home se dibuja como software: ventanas de producto flotando sobre blanco, sin una sola ilustración.

Su paleta real son once negros azulados, nueve blancos y un único azul, #266DF0, que no pinta ni un botón: sólo links, focus rings y el borde de la fila seleccionada. El CTA primario es #202124 y lleva encima un degradado radial cuyo foco está fuera del botón, diez por ciento por encima de su borde superior. En hover, ese foco se enciende. Y se enciende de verdad: los dos stops están registrados con @property, así que el navegador los interpola.

Ver los componentes
light mode 7 capas de sombra 28 colores 4 familias tipográficas cuerpo en peso 500 entrar 50ms · salir 300ms @property 3 temas: root · dark · blue
↓ Scroll
attio.com
Captura del hero de Attio: canvas blanco, titular 'Welcome to agentic revenue.' y dos CTAs, el primario en negro
El canvasBlanco puro #FFFFFF. No hay color de fondo de marca: el color entra sólo por los mocks de producto y por un wash lavanda muy tenue abajo.
Los dos CTAsTalk to sales es un outline de borde #CAD0D9. Start for free es negro #202124 con borde #505967. Ni rastro de azul.
La capturaTomada con JavaScript deshabilitado: 44 nodos del hero llegan del servidor con opacity:0 y hubo que neutralizarlos para que el titular se viera.
01

Color

11 negros · 9 blancos · 1 azul
La rampa de negros — ninguno es negro
0
50
100
200
300
400
500
600
700
800
900

Mira los canales. En todos, el azul supera al rojo: #1C1D1F es rgb(28, 29, 31), #505967 es rgb(80, 89, 103), #6F7988 es rgb(111, 121, 136). La diferencia crece según bajas por la rampa. Sólo black-0 es negro puro, y su único trabajo es el fondo del banner superior. Meter un gris neutro (#71717A) en este sistema desafina inmediatamente contra el azul.

La rampa de blancos
100
200
300
400
500
600
700
800
900

El canvas es #FFFFFF, no un gris casi blanco. Attio no usa un fondo "roto": usa blanco puro y deja que las secciones alternas bajen a #FAFAFB. El paso entre 100 y 200 es de un 2% de luminosidad, y aun así se ve, porque no hay ruido de color compitiendo.

La rampa de azules — el único color del sistema
100
200
300
400
450
500
600
800
La regla del azul. #266DF0 no pinta ni un solo botón en el tema por defecto. Su trabajo entero es: link-foreground, el focus-ring al 30% (#266DF04D), el accent-stroke y el borde de 1.08px de la fila seleccionada en los mocks. Si pintas un CTA de azul, el sistema deja de leerse como Attio y empieza a leerse como cualquier otro SaaS. El botón de Attio es negro.
Los cuatro colores que hacen el trabajo
#266DF0
Azul Attio · blue-500
links, focus ring, accent-stroke, fila seleccionada. Nunca un botón
sobre blanco: 4.6:1 AA · blanco encima: 4.6:1 AA
#202124
El CTA · black-200
el botón primario, en reposo. Y secondary-background en el tema oscuro
#F3F4F6 encima: 15.0:1 AAA
#505967
El borde del CTA · black-600
y el foco que se enciende en hover. Y tertiary-foreground
sobre blanco: 7.6:1 AAA
#6F7988
Accent foreground · black-700
el párrafo del hero. Y el borde del outline en hover
sobre blanco: 4.6:1 — AA en el filo. Nunca bajarlo de 16px
Capa semántica — el mismo token, tres temas
Rol (--internal-color-…):root (claro).darkPara qué
primary-background#FFFFFF#101010El canvas
secondary-background#FAFAFB#1C1D1FSecciones que alternan
surface-subtle#F3F4F6#232529Fondo del ghost en hover
surface#EDEFF3#2E3238Fondo del ghost al pulsar
weak-stroke#EDEFF3#232529El borde de la píldora
subtle-stroke#E4E7EC#2E3238El divisor por defecto
default-stroke#D3D8DF#6F7988El borde del input
strong-stroke#CAD0D9#8F99A8El borde del outline
accent-stroke#709FF5#709FF5El único rol que no cambia con el tema
primary-foreground#1C1D1F#FFFFFFLa tinta
secondary-foreground#232529#EDEFF3El texto de la píldora
tertiary-foreground#505967#B5BDC9Texto de apoyo
accent-foreground#6F7988#8F99A8El párrafo del hero
caption-foreground#A4ADBA#505967Metadatos. 2.4:1 — decorativo
link-foreground#266DF0#709FF5Links
focus-ring#266DF04D (30%)#709FF599 (60%)El azul, tocando un control
Estado y bordes con alfa
#0FC27B
Success
deals ganados. Par profundo: #0DB472
2.1:1 sobre blanco: sólo relleno de badge
#FF5B59
Error
destructivo, deals perdidos. Par: #F65351
3.0:1: texto grande o badge
#F5B900
Warning
deals en riesgo. Par: #DBA601
1.8:1: nunca texto. Sólo relleno
rgba(0,0,0,.05)
Borde de tarjeta
alfa, no token: se oscurece con lo que haya debajo. Invisible sobre blanco, definido sobre una captura
black-0 al 5% · el borde de la baraja es black-500 al 10%
02

Typography

4 familias · cuerpo en 500
AaGgRr266.
Inter Tight Original: interDisplay + inter + tiemposText + JetBrains Mono -> HTML: Inter Tight (display) + Inter (body, idéntica) + Source Serif 4 (serif) + JetBrains Mono (mono, idéntica) — Google substitute
Dos de las cuatro familias son idénticas al original. Attio usa Inter para el texto y JetBrains Mono para el código, y ambas son libres y están en Google Fonts: cero pérdida. La sustitución sólo afecta a dos: interDisplay (el corte óptico grande de Inter, que no está publicado en Google Fonts) se sustituye por Inter Tight, el corte público más cercano — mismo esqueleto, apertura reducida y tracking más apretado, que es exactamente lo que hace un corte display a 80px. Y tiemposText (Klim Type Foundry, comercial) se sustituye por Source Serif 4, que comparte su ADN: serif de texto, contraste moderado, ojo grande, pensada para leerse a 16–20px y no para decorar.
400 · el serif editorialWelcome to agentic revenue
500 · EL CUERPO (por defecto)Welcome to agentic revenue
600 · todos los titularesWelcome to agentic revenue
700 · el techo. No hay 800Welcome to agentic revenue
Body · 16px / 500 / lh 22px / tracking −0.01em

El cuerpo de Attio pesa 500, no 400. Cada token de texto del sistema trae --text-*--font-weight: 500 de fábrica: no hay que pedirlo. No es un descuido, es lo que hace que un CRM lleno de tablas se lea denso y firme en lugar de acuoso. El peso 400 existe, pero hay que invocarlo a mano y prácticamente sólo aparece en el serif.

Serif · Source Serif 4 / 400 / lh 30px

El serif es la única familia del sistema que va a peso 400 y con tracking cero. Aparece en citas y en las notas del changelog, nunca en la interfaz.

La escala. Fíjate en la columna de peso
TokenTamañoLine-heightPesoTracking
text-xs12px18px5000
text-sm14px20px500−0.005em
text-base16px22px500−0.01em
text-lg18px24px500−0.01em
text-xl20px26px500−0.01em
text-2xl24px30px500−0.01em
heading-xs28px34px600−0.01em
heading-sm32px36px600−0.01em
heading-md40px44px600−0.01em
heading-lg56px60px600−0.015em
heading-xl64px64px600−0.02em

La fila resaltada es la única con tracking 0. A 12px, apretar el texto lo hace ilegible, así que la curva se detiene. En todo lo demás, la regla es monótona: más grande, más apretado.

El titular del hero no usa ningún token de la escala, y es fluido respecto a la altura del viewport, no a la anchura:

font-size:     clamp(64px, calc(16px + 5.333svh), 80px);
letter-spacing: clamp(-2.4px, calc(2.08px - 0.3733svh), -1.28px);
line-height: 0.95;

Dos decisiones raras y deliberadas. Primera: svh, no vw. El titular crece con la altura de la pantalla, para que en un portátil de pantalla baja el hero encoja y los mocks de producto sigan cabiendo debajo. La composición manda sobre la tipografía. Segunda: el tracking va al revés que el tamaño. Cuando svh sube, el tamaño sube (+5.333/unidad) y el tracking baja (−0.3733/unidad, ya en negativo). Es la regla óptica clásica, escrita como una interpolación lineal en CSS.
03

Components

todo es real · pasa el ratón
01 · La escalera — la firma real de Attio

Attio no tiene una sombra. Tiene siete, y cada una es una función exacta de la anterior. Lee las columnas: el desplazamiento va 1, 2, 4, 8, 16, 32, 64. El blur va 3, 4, 8, 16, 32, 64, 128. El spread va 0, −1, −2, −4, −8, −16, −32. Y la opacidad va 1%, 2%, 3%, 4%, 5%, 6%, 7%. Cuatro progresiones geométricas en paralelo, y una aritmética para la opacidad. No es una sombra que alguien afinó a ojo: es una fórmula.

capa 10 1px 3px 0
rgba(0,0,0,.01)
capa 20 2px 4px −1px
rgba(0,0,0,.02)
capa 30 4px 8px −2px
rgba(0,0,0,.03)
capa 40 8px 16px −4px
rgba(0,0,0,.04)
capa 50 16px 32px −8px
rgba(0,0,0,.05)
capa 60 32px 64px −16px
rgba(0,0,0,.06)
capa 70 64px 128px −32px
rgba(0,0,0,.07)

Y no se usan sueltas. Se usan acumuladas. shadow-attio-6 son seis sombras a la vez, y ninguna pasa del 6% de negro:

1capa 1
2capas 1–2
3capas 1–3
4capas 1–4
5capas 1–5
6capas 1–6
7capas 1–7

Attio construye profundidad apilando capas casi invisibles, cada una el doble de grande y un uno por ciento más oscura que la anterior. El resultado se comporta como la luz real: nítida y densa cerca del objeto, enorme y difusa lejos. Una sola sombra de 0 20px 40px rgba(0,0,0,.15) nunca hace eso — se ve como una mancha.

Y elevar es subir un peldaño, no cambiar la sombra. El hover de una tarjeta pasa de shadow-3 a shadow-4: las tres capas que ya estaban no se tocan, sólo se añade la cuarta. Como la cuarta es el doble de la tercera, la caída duplica su alcance sin que la base se mueva. La tarjeta se despega del papel.

reposo   shadow-attio-3  →  capas 1, 2, 3
hover    shadow-attio-4  →  capas 1, 2, 3, 4
popover  shadow-attio-5  →  capas 1, 2, 3, 4, 5
modal    shadow-attio-6  →  capas 1, 2, 3, 4, 5, 6
02 · Los CTAs — 36px de alto, radio 10px, y un foco que amanece

Geometría compartida (medida): alto 36px (h-9), radio 10px, padding 0 12px, gap 6px, borde de 1px siempre presente (aunque sea transparente), label de 14px / 500 / lh 20px / tracking −0.005em. La variante grande — la del hero y la de móvil — sube a 46px (h-11.5), radio 12px, padding 0 14px, label de 16px.

El radio 10px no es un token. Los radios del sistema son 2/4/6/8/12/16/20. El botón usa rounded-[10px], un valor arbitrario que cae justo entre lg (8) y xl (12). Alguien lo miró a 36px de alto y decidió que 8 era demasiado cuadrado y 12 demasiado blando. Ese medio punto es el sitio.

El icono cambia el padding, no el layout. Un icono es ópticamente más ligero que una letra, así que necesita menos aire de su lado. Attio lo resuelve en el propio botón, sin variantes ni props: .button:has(> svg:last-child) { padding-right: 8px; }. Compara arriba "Start for free" con "Crear deal →": el padding derecho baja de 12px a 8px.

El primario: un foco colocado fuera del botón

El primario no es azul. Es #202124 con borde #505967. Y lleva encima un radial-gradient(at center -10%, …): el centro del degradado está fuera del botón, un diez por ciento por encima de su borde superior. En reposo, los dos stops valen lo mismo — un radial de #202124 a #202124 es un rectángulo plano. El degradado está montado, pero apagado.

reposofrom: #202124
to:   #202124
plano
hover — el foco se enciendefrom: #505967
to:   #202124
50ms
activefrom: #383E47
to:   #383E47
plano otra vez, más claro

En hover sólo cambia --cta-from, que es el stop del centro del radial. Al aclararse a #505967, aparece un foco que entra desde arriba y se desvanece hacia los bordes inferiores. El botón no cambia de color: se ilumina.

Y funciona porque los dos stops están registrados: @property --cta-from { syntax: "<color>"; inherits: false; }. Sin @property, una custom property es una cadena de texto y el navegador la cambia de golpe — el botón parpadearía. Con syntax: "<color>", el navegador sabe que es un color y lo interpola. El degradado se anima. Es la única forma de animar un background-image sin recurrir a un pseudo-elemento y un opacity.

La asimetría del tiempo

Entrar (hover / active)
50ms
El botón responde de inmediato al ratón. Prácticamente instantáneo.
Salir (volver a reposo)
300ms
Y se apaga despacio cuando te vas. La curva es cubic-bezier(.2, 0, 0, 1).

Entrar cuesta 50ms. Salir cuesta 300ms. Es lo contrario del transition: all .2s simétrico que sale por defecto en todas partes, y es exactamente la diferencia entre un control que se siente vivo y uno que se siente laggy. La curva por defecto, cubic-bezier(.2, 0, 0, 1), tiene los dos puntos de control pegados a los extremos verticales: arranca casi de golpe y frena larguísimo. Se parece más a algo con masa que a una interpolación.

La regla de los tres: una propiedad por variante

reposo · borde #CAD0D9
hover · borde #6F7988
active · fondo #F3F4F6
disabled · #E4E7EC

Outline: en hover no pasa nada más que el borde. Ni el fondo, ni el texto, ni una sombra, ni un transform. El borde salta de #CAD0D9 (white-800) a #6F7988 (black-700): cruza de la rampa de blancos a la de negros de un salto. Es un cambio de contraste enorme — de 1.4:1 a 4.6:1 contra el blanco — hecho con un solo valor. El relleno #F3F4F6 se reserva para confirmar la acción al pulsar, no para anticiparla.

Ghost: el espejo exacto. Aquí sólo se mueve el fondo (transparente → #F3F4F6#EDEFF3). Y el borde sigue existiendo, transparente, para que el botón no cambie de tamaño ni empuje a sus vecinos al convertirse en otra variante.

Primario: se mueve el degradado. Outline: se mueve el borde. Ghost: se mueve el fondo. Cada variante tiene exactamente una propiedad que reacciona, y ninguna se repite. Y ningún botón de Attio anima transform: no escalan, no se desplazan, no ganan sombra en hover.

03 · Las tarjetas — un fondo, un alfa, un radio y N capas

Una tarjeta de Attio es fondo blanco + borde de un alfa muy bajo + radio 12px + N capas de la escalera. No hay más. La variación entre tarjetas es la N. Pasa el ratón: cada una sube un peldaño.

shadow-attio-3 → 4
Tarjeta base

Radio 12px, padding 24px, gap 16px, borde rgba(0,0,0,.05). Tres capas en reposo, cuatro en hover. El borde es un alfa, no un token: se oscurece con lo que haya debajo.

rounded-xl
shadow-attio-5 → 6
Popover

Cinco capas. La quinta es 0 16px 32px −8px al 5%: el primer peldaño donde la caída empieza a leerse como distancia y no como contacto.

dropdown

El radio cambia con el breakpoint, y el borde también. En móvil la tarjeta es rounded-md (6px) y no tiene borde; a partir de lg (992px) sube a rounded-xl (12px) y aparece el lg:border lg:border-black-0/5. Una tarjeta que ocupa el ancho completo de un móvil no necesita borde: sus límites son los del viewport.

La fila de producto — la única sombra fuera de la escalera

GL GreenLeaf· Enterprise En negociación $128,400
RM Ramp· Mid-market Ganado $64,000
NV Novo Health· Enterprise En riesgo $212,750
AX Axiom Labs· Startup Perdido $18,200

Tres detalles medidos, y ninguno es un accidente:

1 · El borde mide 1.08px. No es un error de redondeo. La maqueta de producto se sirve a una escala fraccionaria: en móvil el mismo bloque lleva 0.54px, exactamente la mitad. Attio escaló el diseño de la app real y arrastró el borde con él en lugar de redondearlo a 1px. Lo mismo con el padding: 6.5px en móvil, 13px en desktop. Es la misma fila de la app, a otra escala.

2 · El halo es azul. 0 0 2px rgba(28, 40, 64, 0.18) — un difuso sin desplazamiento, teñido de azul marino, no de negro. Sobre un canvas frío, un halo negro se vería sucio. Es la única sombra del sitio que no sale de la escalera.

3 · El borde de selección es el azul de marca, #266DF0. Es el único sitio, junto al focus ring y a los links, donde el azul toca algo. Pasa el ratón por la tabla: el borde se aclara a #709FF5 (accent-stroke) y cada fila coge #F3F4F6 de fondo.

La baraja — 3° y 6°

Pipeline · Q3
Detrás de cada mock hay dos rectángulos girados y . Una progresión aritmética, igual que todo lo demás.

El borde de las cartas del fondo es rgba(56, 62, 71, 0.1) — el black-500 al 10%. Con alfa, no opaco, para que las cartas se oscurezcan entre sí al solaparse. Un color sólido habría hecho que las tres se vieran igual de fuertes, y la baraja habría perdido la profundidad.

El panel de cristal — el único glassmorphism, y se apaga

// backdrop-filter: blur(12px)
Llamada con GreenLeaf
Fondo rgba(243, 244, 246, 0.8) — white-300 al 80%. Radio 16px. Padding 6px, y padding-top: 0.

El blur se desactiva durante el scroll. [data-hero-scrolling] .panel { backdrop-filter: none; }. backdrop-filter es caro y a 60fps se nota. Attio lo apaga mientras el hero se mueve y lo vuelve a encender al parar. Es la clase de decisión que sólo tomas si has medido el frame budget.

04 · La píldora y los inputs — dos elementos, dos responsabilidades

La píldora del hero son dos elementos. El <a> exterior lleva sólo el borde (1px solid #EDEFF3, radio 13px, y en :active pasa a #E4E7EC). El <div> interior lleva sólo el fondo (blanco, y en hover #FBFBFC). Ninguno de los dos hace las dos cosas. Es la misma disciplina que separa el outline del ghost, aplicada dentro de un único componente.

#FBFBFC no es un token. Está entre white-100 (#FFFFFF) y white-200 (#FAFAFB) — es un valor arbitrario todavía más sutil que el paso más pequeño de la rampa. Un cambio de 1.5% de luminosidad. Y el radio, 13px, tampoco está en la escala.

El padding es asimétrico: 5px 7px 5px 11px. Once píxeles a la izquierda, donde va el texto; siete a la derecha, donde va el chevron de 12×12. El mismo principio óptico que el :has() de los botones, aquí escrito a mano.

El input comparte la geometría del botón: 36px de alto, radio 10px, padding 0 12px, label de 14px. En reposo el borde es default-stroke (#D3D8DF); en hover sube a #6F7988, igual que el outline. Y al enfocar aparece el único sitio del sistema donde el azul toca un control: box-shadow: 0 0 0 3px #266DF04D — el blue-500 al 30%. Dale al tabulador y míralo.

El primario, además del ring, aplica su estado de hover en :focus-visible: el foco del degradado se enciende igual. El teclado ve exactamente lo mismo que el ratón.

04

Spacing

base 4px · lg = 992px
Escala — todo es n × 4px
14px · gap de la píldora
1.56px · gap del botón, padding del cristal
28px · gap del botón grande, padding con icono
312px · padding del botón
3.514px · padding del botón grande
416px · gap de tarjeta
624px · padding de tarjeta
936px · la altura del botón
11.546px · la altura del botón grande
1248px · el banner superior
1768px · la altura de la nav
2080px · padding de columna
El header es una suma de piezas que pueden valer 0
--site-header-height: calc(
    var(--polaris-toolbar-height)  +  36px | 0
    var(--nav-height)             +  68px | 52px
    var(--subheader-height)     +  63px | 0
    var(--banner-height)             48px | 0
);

Cada franja tiene su par -visible-height / -hidden-height: 0px, y mostrar u ocultar una es reasignar una variable. Nada más abajo en la página necesita enterarse: el scroll-margin global de las anclas ya se calcula contra --site-header-height.

Radios — tres se salen de la escala, a propósito
2px
xs
4px
sm
6px
md
8px
lg
10px
botón
12px
xl
13px
píldora
15px
fila app
16px
2xl
20px
3xl

Los tres marcados en azul — 10, 13 y 15no están en la escala (2/4/6/8/12/16/20). No es descuido: son los tres componentes que Attio afinó ópticamente uno por uno. El botón (10px, entre lg y xl), la píldora (13px) y la fila de la app (15px, que arrastra la escala del producto real). Todo lo demás cae limpiamente en la escala.

Contenedores y breakpoints
--container-2xl: 42rem  · 672px
--container-4xl: 56rem  · 896px
--container-5xl: 64rem  · 1024px
--container-6xl: 72rem  · 1152px
--container-7xl: 80rem  · 1280px

--breakpoint-lg: 992px  · NO 1024
El breakpoint lg es 992px, no el 1024 por defecto de Tailwind. Attio lo bajó 32px. Es el punto exacto donde una tabla de CRM con la barra lateral desplegada deja de caber, y ese punto no lo decide el framework: lo decide el producto.
05

Principles

guardrails

    Do

  • Apila la escalera. Elevar es añadir la siguiente capa, nunca cambiar la sombra: shadow-3shadow-4 en hover.
  • El CTA primario es negro (#202124), con borde #505967. Siempre.
  • Registra los stops del degradado con @property si quieres el hover del primario. Sin eso, el botón parpadea en lugar de encenderse.
  • Entra en 50ms, sal en 300ms. La asimetría es el sistema, no un detalle.
  • Peso 500 para el cuerpo. Es el valor por defecto de todos los tokens de texto.
  • Tracking negativo, y más negativo cuanto más grande. Salvo a 12px, donde vuelve a 0.
  • Una propiedad por variante de botón. Primario: el degradado. Outline: el borde. Ghost: el fondo.
  • Bordes de tarjeta con alfa (rgba(0,0,0,.05)), para que se oscurezcan con lo que hay debajo.
  • Apaga backdrop-filter durante el scroll. Es caro y a 60fps se nota.
  • Tiñe los halos de azul (rgba(28,40,64,.18)). Sobre un canvas frío, un halo negro se ve sucio.

    Don't

  • No pintes un botón de azul. #266DF0 es para links, focus ring y accent-stroke. Un CTA azul mata la identidad.
  • No animes transform en un control. Attio no escala ni desplaza ningún botón ni ninguna tarjeta. La retroalimentación es cromática y de elevación.
  • No uses una sombra de una sola capa. 0 4px 12px rgba(0,0,0,.1) no es de este sistema: se ve como una mancha.
  • No subas la opacidad de una capa. Ninguna pasa del 7%. Si necesitas más peso, añade otra capa.
  • No uses negro puro salvo en el banner. Los once "negros" de la rampa llevan azul dentro.
  • No uses grises neutros ni cálidos. Un #71717A desafina contra esta rampa al instante.
  • No pongas accent-foreground (#6F7988) por debajo de 16px. Está en el filo de AA (4.6:1).
  • No uses transiciones simétricas. Un transition: all .2s ease deshace la mitad del carácter del sistema.
  • No redondees el 1.08px de la fila de producto a 1px. Rompe la correspondencia de escala con la app real.
  • No uses #A4ADBA para texto pequeño. 2.4:1 — es un caption, no un párrafo.
06

Resources

copy & paste
/* Los dos stops del CTA, registrados como COLOR.
   Sin esto el degradado no se puede animar: una custom property
   sin @property es una CADENA DE TEXTO y el navegador la cambia de golpe. */
@property --cta-from { syntax: "<color>"; inherits: false; initial-value: #202124; }
@property --cta-to   { syntax: "<color>"; inherits: false; initial-value: #202124; }

:root {
  /* --- rampa de negros: once pasos, TODOS con azul dentro --- */
  --color-black-0:   #000000;   /* sólo el banner superior */
  --color-black-50:  #101010;   /* el canvas del tema oscuro */
  --color-black-100: #1C1D1F;   /* LA TINTA */
  --color-black-200: #202124;   /* EL CTA */
  --color-black-300: #232529;   /* secondary-foreground */
  --color-black-400: #2E3238;   /* el label del outline y del ghost */
  --color-black-500: #383E47;   /* el :active del CTA. Al 10%, el borde de la baraja */
  --color-black-600: #505967;   /* EL BORDE DEL CTA. Y el foco que se enciende */
  --color-black-700: #6F7988;   /* accent-foreground. Y el outline en hover */
  --color-black-800: #8F99A8;
  --color-black-900: #A4ADBA;   /* caption. 2.4:1 — nunca texto pequeño */

  /* --- rampa de blancos --- */
  --color-white-100: #FFFFFF;   /* EL CANVAS. Blanco puro, no un gris roto */
  --color-white-200: #FAFAFB;   /* secciones que alternan */
  --color-white-300: #F3F4F6;   /* el ghost en hover. El label del CTA */
  --color-white-400: #EDEFF3;   /* surface. Y el borde de la píldora */
  --color-white-500: #E4E7EC;   /* el divisor */
  --color-white-600: #DEE2E7;
  --color-white-700: #D3D8DF;   /* el borde del input */
  --color-white-800: #CAD0D9;   /* EL BORDE DEL OUTLINE */
  --color-white-900: #B5BDC9;   /* el CTA deshabilitado */

  /* --- rampa de azules: el ÚNICO color. No pinta ni un botón --- */
  --color-blue-100: #E8F0FF;
  --color-blue-200: #C8DCFF;
  --color-blue-300: #94B9FF;
  --color-blue-400: #709FF5;    /* accent-stroke: el único rol que NO cambia con el tema */
  --color-blue-450: #538BF3;    /* el CTA del tema .blue */
  --color-blue-500: #266DF0;    /* EL AZUL: links, focus ring, fila seleccionada */
  --color-blue-600: #245BC2;
  --color-blue-800: #1A2233;

  /* --- estado --- */
  --color-green-500: #0FC27B;   --color-green-600: #0DB472;
  --color-red-500:   #FF5B59;   --color-red-600:   #F65351;
  --color-yellow-500:#F5B900;   --color-yellow-600:#DBA601;

  /* --- LA ESCALERA. cada peldaño DOBLA el anterior y suma 1% --- */
  --shadow-attio-layer-1: 0px  1px   3px  0px  rgba(0,0,0,0.01);
  --shadow-attio-layer-2: 0px  2px   4px -1px  rgba(0,0,0,0.02);
  --shadow-attio-layer-3: 0px  4px   8px -2px  rgba(0,0,0,0.03);
  --shadow-attio-layer-4: 0px  8px  16px -4px  rgba(0,0,0,0.04);
  --shadow-attio-layer-5: 0px 16px  32px -8px  rgba(0,0,0,0.05);
  --shadow-attio-layer-6: 0px 32px  64px -16px rgba(0,0,0,0.06);
  --shadow-attio-layer-7: 0px 64px 128px -32px rgba(0,0,0,0.07);

  /* la única sombra fuera de la escalera: el halo AZULADO de la fila de producto */
  --shadow-attio-product-e1: 0 1px 3px rgba(0,0,0,0.04), 0 0 2px rgba(28,40,64,0.18);

  /* --- radios. 10 (botón), 13 (píldora) y 15 (fila) NO están en la escala --- */
  --radius-xs: 2px;  --radius-sm: 4px;  --radius-md: 6px;  --radius-lg: 8px;
  --radius-xl: 12px; --radius-2xl: 16px; --radius-3xl: 20px;

  /* --- tipografía. OJO: el peso por defecto del CUERPO es 500 --- */
  --font-inter:         "inter";          /* -> Inter          (Google, idéntica) */
  --font-inter-display: "interDisplay";   /* -> Inter Tight    (Google, sustituto) */
  --font-tiempos-text:  "tiemposText";    /* -> Source Serif 4 (Google, sustituto) */
  --font-jetbrains-mono:"JetBrains Mono"; /* -> JetBrains Mono (Google, idéntica) */

  --text-xs:   0.75rem;  --text-xs--line-height:  1.125rem; --text-xs--font-weight: 500;  --text-xs--letter-spacing: 0;
  --text-sm:   0.875rem; --text-sm--line-height:  1.25rem;  --text-sm--font-weight: 500;  --text-sm--letter-spacing: -.005em;
  --text-base: 1rem;     --text-base--line-height:1.375rem; --text-base--font-weight: 500;--text-base--letter-spacing: -.01em;
  --text-lg:   1.125rem; --text-lg--line-height:  1.5rem;   --text-lg--font-weight: 500;  --text-lg--letter-spacing: -.01em;
  --text-xl:   1.25rem;  --text-xl--line-height:  1.625rem; --text-xl--font-weight: 500;  --text-xl--letter-spacing: -.01em;
  --text-2xl:  1.5rem;   --text-2xl--line-height: 1.875rem; --text-2xl--font-weight: 500; --text-2xl--letter-spacing: -.01em;

  --text-heading-xs: 1.75rem; --text-heading-xs--line-height: 2.125rem; --text-heading-xs--font-weight: 600; --text-heading-xs--letter-spacing: -.01em;
  --text-heading-sm: 2rem;    --text-heading-sm--line-height: 2.25rem;  --text-heading-sm--font-weight: 600; --text-heading-sm--letter-spacing: -.01em;
  --text-heading-md: 2.5rem;  --text-heading-md--line-height: 2.75rem;  --text-heading-md--font-weight: 600; --text-heading-md--letter-spacing: -.01em;
  --text-heading-lg: 3.5rem;  --text-heading-lg--line-height: 3.75rem;  --text-heading-lg--font-weight: 600; --text-heading-lg--letter-spacing: -.015em;
  --text-heading-xl: 4rem;    --text-heading-xl--line-height: 4rem;     --text-heading-xl--font-weight: 600; --text-heading-xl--letter-spacing: -.02em;

  /* --- curvas. la de por defecto arranca de golpe y frena larguísimo --- */
  --ease-in-out:       cubic-bezier(.2, 0, 0, 1);      /* LA de por defecto */
  --ease-out:          cubic-bezier(0, 0, 0, 1);
  --ease-in:           cubic-bezier(.3, 0, 1, 1);
  --ease-in-out-cubic: cubic-bezier(.65, 0, .35, 1);   /* acordeones, 300ms */
  --ease-out-cubic:    cubic-bezier(.33, 1, .68, 1);   /* fade de navegación, 100ms */
  --ease-in-out-quad:  cubic-bezier(.45, .05, .55, .95); /* diálogos, 200ms */
  --ease-in-out-expo:  cubic-bezier(1, 0, 0, 1);
  --ease-reveal:       cubic-bezier(0, 0, .58, 1);

  /* --- espaciado. base 4px. Y el breakpoint lg es 992, no 1024 --- */
  --spacing: 0.25rem;
  --breakpoint-lg: 992px;
  --site-header-nav-height: 68px;
  --site-header-banner-visible-height: 48px;
}

/* --- capa semántica: los componentes no conocen colores, conocen ROLES --- */
:root {
  --internal-color-primary-background:    var(--color-white-100);
  --internal-color-secondary-background:  var(--color-white-200);
  --internal-color-surface-subtle:        var(--color-white-300);
  --internal-color-surface:               var(--color-white-400);
  --internal-color-muted-background:      var(--color-white-400);
  --internal-color-muted-strong-background: var(--color-white-700);
  --internal-color-weak-stroke:           var(--color-white-400);
  --internal-color-subtle-stroke:         var(--color-white-500);
  --internal-color-default-stroke:        var(--color-white-700);
  --internal-color-strong-stroke:         var(--color-white-800);
  --internal-color-accent-stroke:         var(--color-blue-400);
  --internal-color-primary-foreground:    var(--color-black-100);
  --internal-color-secondary-foreground:  var(--color-black-300);
  --internal-color-tertiary-foreground:   var(--color-black-600);
  --internal-color-accent-foreground:     var(--color-black-700);
  --internal-color-caption-foreground:    var(--color-black-900);
  --internal-color-disabled-foreground:   var(--color-white-700);
  --internal-color-link-foreground:       var(--color-blue-500);
  --internal-color-link-strong-foreground:var(--color-blue-600);
  --internal-color-focus-ring:            #266DF04D;   /* blue-500 al 30% */
}

.dark {
  --internal-color-primary-background:    var(--color-black-50);
  --internal-color-secondary-background:  var(--color-black-100);
  --internal-color-surface-subtle:        var(--color-black-300);
  --internal-color-surface:               var(--color-black-400);
  --internal-color-weak-stroke:           var(--color-black-300);
  --internal-color-subtle-stroke:         var(--color-black-400);
  --internal-color-default-stroke:        var(--color-black-700);
  --internal-color-strong-stroke:         var(--color-black-800);
  --internal-color-accent-stroke:         var(--color-blue-400);  /* NO cambia */
  --internal-color-primary-foreground:    var(--color-white-100);
  --internal-color-secondary-foreground:  var(--color-white-400);
  --internal-color-tertiary-foreground:   var(--color-white-900);
  --internal-color-accent-foreground:     var(--color-black-800);
  --internal-color-caption-foreground:    var(--color-black-600);
  --internal-color-link-foreground:       var(--color-blue-400);
  --internal-color-link-strong-foreground:var(--color-blue-300);
  --internal-color-focus-ring:            #709FF599;   /* blue-400 al 60% */
}
/* Attio usa Tailwind v4 con @theme. Los tokens viven en :root, .dark y .blue —
   TRES ámbitos de tema, no dos. */

@import "tailwindcss";

@theme {
  --color-black-0:   #000000;
  --color-black-50:  #101010;
  --color-black-100: #1C1D1F;
  --color-black-200: #202124;
  --color-black-300: #232529;
  --color-black-400: #2E3238;
  --color-black-500: #383E47;
  --color-black-600: #505967;
  --color-black-700: #6F7988;
  --color-black-800: #8F99A8;
  --color-black-900: #A4ADBA;

  --color-white-100: #FFFFFF;
  --color-white-200: #FAFAFB;
  --color-white-300: #F3F4F6;
  --color-white-400: #EDEFF3;
  --color-white-500: #E4E7EC;
  --color-white-600: #DEE2E7;
  --color-white-700: #D3D8DF;
  --color-white-800: #CAD0D9;
  --color-white-900: #B5BDC9;

  --color-blue-400: #709FF5;
  --color-blue-450: #538BF3;
  --color-blue-500: #266DF0;
  --color-blue-600: #245BC2;

  --shadow-attio-layer-1: 0px  1px   3px  0px  --alpha(var(--color-black-0) / 1%);
  --shadow-attio-layer-2: 0px  2px   4px -1px  --alpha(var(--color-black-0) / 2%);
  --shadow-attio-layer-3: 0px  4px   8px -2px  --alpha(var(--color-black-0) / 3%);
  --shadow-attio-layer-4: 0px  8px  16px -4px  --alpha(var(--color-black-0) / 4%);
  --shadow-attio-layer-5: 0px 16px  32px -8px  --alpha(var(--color-black-0) / 5%);
  --shadow-attio-layer-6: 0px 32px  64px -16px --alpha(var(--color-black-0) / 6%);
  --shadow-attio-layer-7: 0px 64px 128px -32px --alpha(var(--color-black-0) / 7%);

  --breakpoint-lg: 992px;    /* NO 1024 */

  --ease-in-out: cubic-bezier(.2, 0, 0, 1);
}

/* Las utilidades de sombra son ACUMULATIVAS: shadow-attio-N = capas 1..N */
@utility shadow-attio-2 { box-shadow: var(--shadow-attio-layer-1), var(--shadow-attio-layer-2); }
@utility shadow-attio-3 { box-shadow: var(--shadow-attio-layer-1), var(--shadow-attio-layer-2),
                                      var(--shadow-attio-layer-3); }
@utility shadow-attio-4 { box-shadow: var(--shadow-attio-layer-1), var(--shadow-attio-layer-2),
                                      var(--shadow-attio-layer-3), var(--shadow-attio-layer-4); }
@utility shadow-attio-5 { box-shadow: var(--shadow-attio-layer-1), var(--shadow-attio-layer-2),
                                      var(--shadow-attio-layer-3), var(--shadow-attio-layer-4),
                                      var(--shadow-attio-layer-5); }
@utility shadow-attio-6 { box-shadow: var(--shadow-attio-layer-1), var(--shadow-attio-layer-2),
                                      var(--shadow-attio-layer-3), var(--shadow-attio-layer-4),
                                      var(--shadow-attio-layer-5), var(--shadow-attio-layer-6); }

/* --- LAS CLASES REALES DEL BOTÓN, tal cual salen del HTML servido --- */

/* base (desktop) */
class="relative inline-flex cursor-pointer items-center justify-center text-nowrap
       border transition-colors duration-300 ease-in-out
       hover:duration-50 active:duration-50
       disabled:pointer-events-none disabled:cursor-default
       h-9 gap-x-1.5 rounded-[10px] px-3 text-sm
       has-[>svg:last-child,>img:last-child]:pr-2
       has-[>svg:first-child,>img:first-child]:pl-2
       max-lg:h-11.5 max-lg:gap-x-2 max-lg:rounded-xl max-lg:px-3.5 max-lg:text-base
       button-primary"

/* la tarjeta */
class="overflow-hidden rounded-md bg-primary-background
       lg:rounded-xl lg:border lg:border-black-0/5"

/* la fila de producto */
class="relative z-10 flex h-full w-full flex-col gap-px
       rounded-[7px] border-[0.54px] bg-white-100 p-[3px]
       lg:rounded-[15px] lg:border-[1.08px] lg:p-[6.5px]
       border-[#266df0] shadow-attio-product-e1"

/* el panel de cristal, y el blur que se APAGA al hacer scroll */
class="absolute touch-none select-none overflow-hidden
       rounded-lg p-[3px] pt-0 backdrop-blur-md
       lg:rounded-2xl lg:p-1.5 lg:pt-0
       group-data-hero-scrolling/hero:backdrop-blur-none
       bg-white-300/80"
/* ===========================================================================
   EL CTA PRIMARIO — el foco que amanece
   =========================================================================== */

@property --button-primary-bg-from { syntax: "<color>"; inherits: false; initial-value: #000; }
@property --button-primary-bg-to   { syntax: "<color>"; inherits: false; initial-value: #000; }

.button-primary {
  /* el foco del radial está en `at center -10%`:
     FUERA del botón, un 10% por encima de su borde superior */
  background-image: radial-gradient(at center -10%,
      var(--button-primary-bg-from)   0%,
      var(--button-primary-bg-to)   100%);
  background-color: var(--button-primary-bg-fallback);
  border-color:     var(--button-primary-border);
  color:            var(--button-primary-text);

  /* interpola las VARIABLES, no una imagen */
  transition-property: color, border-color,
                       --button-primary-bg-from,
                       --button-primary-bg-to;
}

:root .button-primary {
  /* reposo: los dos stops valen LO MISMO -> el degradado está apagado */
  --button-primary-bg-from:     #202124;
  --button-primary-bg-to:       #202124;
  --button-primary-bg-fallback: #202124;
  --button-primary-border:      #505967;
  --button-primary-text:        #F3F4F6;

  /* hover: sólo cambia el stop DE ARRIBA -> el foco se enciende */
  --button-primary-bg-hover-from:     #505967;
  --button-primary-bg-hover-to:       #202124;
  --button-primary-bg-hover-fallback: #383E47;
  --button-primary-border-hover:      #505967;
  --button-primary-text-hover:        #FFFFFF;

  /* focus-visible: EXACTAMENTE lo mismo que el hover. El teclado ve lo que ve el ratón */
  --button-primary-bg-focus-from:     #505967;
  --button-primary-bg-focus-to:       #202124;
  --button-primary-bg-focus-fallback: #383E47;

  /* active: plano otra vez, un peldaño más claro */
  --button-primary-bg-active-from:     #383E47;
  --button-primary-bg-active-to:       #383E47;
  --button-primary-bg-active-fallback: #505967;
  --button-primary-border-active:      #505967;
  --button-primary-text-active:        #FFFFFF;

  --button-primary-bg-disabled-fallback: #B5BDC9;
}

/* ===========================================================================
   OUTLINE — en hover SÓLO se mueve el borde
   =========================================================================== */
:root {
  --button-outline-bg:            #FFFFFF;
  --button-outline-border:        #CAD0D9;   /* white-800 */
  --button-outline-text:          #2E3238;
  --button-outline-bg-hover:      #FFFFFF;   /* el fondo NO cambia */
  --button-outline-border-hover:  #6F7988;   /* black-700: SALTA de rampa */
  --button-outline-text-hover:    #2E3238;   /* el texto NO cambia */
  --button-outline-bg-active:     #F3F4F6;   /* el relleno se reserva para CONFIRMAR */
  --button-outline-border-active: #383E47;
  --button-outline-text-active:   #232529;
  --button-outline-border-disabled: #E4E7EC;
  --button-outline-text-disabled-foreground: #CAD0D9;
}

/* ===========================================================================
   GHOST — el espejo: SÓLO se mueve el fondo
   =========================================================================== */
:root {
  --button-ghost-bg:         transparent;
  --button-ghost-text:       #2E3238;
  --button-ghost-bg-hover:   #F3F4F6;
  --button-ghost-text-hover: #232529;
  --button-ghost-bg-active:  #EDEFF3;
  --button-ghost-text-active:#202124;
  --button-ghost-text-disabled-foreground: #B5BDC9;
}

/* ===========================================================================
   GEOMETRÍA COMPARTIDA — y la asimetría del tiempo
   =========================================================================== */
.button {
  display: inline-flex; align-items: center; justify-content: center;
  height: 36px;                 /* h-9 */
  border-radius: 10px;          /* ARBITRARIO: entre lg (8) y xl (12) */
  padding: 0 12px;
  gap: 6px;
  border: 1px solid;            /* SIEMPRE, aunque sea transparente */
  font-size: 14px; line-height: 20px; font-weight: 500; letter-spacing: -0.005em;

  transition-duration: 300ms;                    /* SALIR: 300ms */
  transition-timing-function: cubic-bezier(.2, 0, 0, 1);
}
.button:hover, .button:active { transition-duration: 50ms; }   /* ENTRAR: 50ms */

/* el icono pesa menos que una letra: menos aire de ese lado */
.button:has(> svg:last-child, > img:last-child)   { padding-right: 8px; }
.button:has(> svg:first-child, > img:first-child) { padding-left:  8px; }

/* variante grande (el CTA del hero, y TODO el móvil) */
.button-lg { height: 46px; border-radius: 12px; padding: 0 14px; gap: 8px; font-size: 16px; }

/* ===========================================================================
   LA TARJETA — y el hover, que es SUBIR UN PELDAÑO
   =========================================================================== */
.card {
  background: #FFFFFF;
  border: 1px solid rgba(0, 0, 0, 0.05);   /* alfa: se oscurece con el fondo */
  border-radius: 12px;                     /* 6px en móvil, y SIN borde */
  padding: 24px;
  gap: 16px;
  box-shadow: var(--shadow-attio-layer-1),
              var(--shadow-attio-layer-2),
              var(--shadow-attio-layer-3);              /* 3 capas */
  transition: box-shadow 300ms cubic-bezier(.2, 0, 0, 1);
}
.card:hover {
  box-shadow: var(--shadow-attio-layer-1),
              var(--shadow-attio-layer-2),
              var(--shadow-attio-layer-3),
              var(--shadow-attio-layer-4);              /* 4: las tres de antes + UNA */
  transition-duration: 50ms;
}

/* ===========================================================================
   LA FILA DE PRODUCTO — la única sombra fuera de la escalera
   =========================================================================== */
.card-product {
  background: #FFFFFF;
  border-radius: 15px;                      /* arbitrario: la escala de la app real */
  border: 1.08px solid transparent;         /* !! 1.08px. En móvil: 0.54px, la mitad */
  padding: 6.5px;                           /* en desktop: 13px. Escala 2x */
  gap: 1px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04),
              0 0 2px   rgba(28, 40, 64, 0.18);   /* halo AZULADO, no negro */
}
.card-product[data-selected] { border-color: #266DF0; }

/* ===========================================================================
   LA PÍLDORA — dos elementos, dos responsabilidades
   =========================================================================== */
.pill {                              /* el <a>: SÓLO el borde */
  border: 1px solid #EDEFF3;
  border-radius: 13px;               /* arbitrario */
  transition: border-color 300ms cubic-bezier(.2, 0, 0, 1);
}
.pill:active { border-color: #E4E7EC; }

.pill > span {                       /* el <div>: SÓLO el fondo */
  display: flex; align-items: center; gap: 4px;
  padding: 5px 7px 5px 11px;         /* asimétrico: 11 al texto, 7 al chevron */
  background: #FFFFFF;
  border-radius: 13px;
  font-size: 13px; line-height: 18px; font-weight: 500;
  color: #232529;
  transition: background-color 300ms cubic-bezier(.2, 0, 0, 1);
}
.pill:hover > span { background: #FBFBFC; }   /* ENTRE white-100 y white-200 */

/* ===========================================================================
   EL INPUT — el único sitio donde el azul toca un control
   =========================================================================== */
.input {
  height: 36px; border-radius: 10px; padding: 0 12px;
  background: #FFFFFF;
  border: 1px solid #D3D8DF;
  font-size: 14px; font-weight: 500; letter-spacing: -0.005em;
  transition: border-color 300ms cubic-bezier(.2, 0, 0, 1),
              box-shadow   300ms cubic-bezier(.2, 0, 0, 1);
}
.input:hover        { border-color: #6F7988; transition-duration: 50ms; }
.input:focus-visible{ border-color: #266DF0; outline: none;
                      box-shadow: 0 0 0 3px #266DF04D; }   /* blue-500 al 30% */
# DESIGN.md - Attio

Sistema de diseño extraído de https://attio.com (home, julio 2026).

## NOTA DE MÉTODO

Attio DETECTA el Chrome DevTools Protocol: cuando Puppeteer controla la pestaña, el
runtime de la página se cuelga y Runtime.callFunctionOn expira. No hay estilos
computados que leer.

Pero el sitio es Next.js SERVER-RENDERED: el HTML llega completo (1.09 MB) y las ocho
hojas de estilo (462 KB) se sirven sin ninguna protección. Todos los valores de este
documento están leídos DIRECTAMENTE del CSS de producción y del HTML servido. Es una
fuente más fiable, no menos: son los tokens tal cual los escribió Attio, antes de que
el navegador los resuelva.

La captura del hero se tomó con JavaScript deshabilitado, neutralizando los 44 nodos
que llegan del servidor con `style="filter:blur(1.5px);opacity:0"` a la espera de la
animación de entrada.

## 1. TEMA VISUAL

Attio vende un CRM y su home ENSEÑA LA APLICACIÓN. No hay ilustración, no hay 3D, no hay
blob de degradado. Hay ventanas de producto flotando sobre un canvas blanco.

Eso obliga a que el marketing y el producto compartan la misma gramática. Y ahí está la
decisión que define el sistema: en lugar de una paleta de marca, Attio construyó una
ESCALERA DE ELEVACIÓN DE SIETE PELDAÑOS donde cada peldaño es una función exacta del
anterior.

  desplazamiento:  1,  2,  4,  8, 16, 32,  64   (x2)
  blur:            3,  4,  8, 16, 32, 64, 128   (x2)
  spread:          0, -1, -2, -4, -8,-16, -32   (x2, negativo)
  opacidad:       1%, 2%, 3%, 4%, 5%, 6%,  7%   (+1)

Cuatro progresiones geométricas en paralelo, y una aritmética. Y se usan ACUMULADAS:
shadow-attio-6 son SEIS sombras a la vez, y ninguna pasa del 6%.

Elevar = AÑADIR LA SIGUIENTE CAPA, no cambiar la sombra.

La segunda firma es el CTA. No es azul: es #202124, un negro con azul dentro, con un
radial-gradient cuyo foco está en `at center -10%` — fuera del botón, por encima. En
reposo los dos stops valen lo mismo (plano). En hover el de arriba salta a #505967 y el
foco SE ENCIENDE. Y se anima de verdad porque los stops están declarados con
@property { syntax: "" }: el navegador los interpola.

Personalidad: exacto, geométrico, de producto, frío, sobrio, denso
Densidad: densa en el control (botones de 36px), amplia en la página
Modo: claro. Existe .dark completo y un tercer tema .blue
Ornamentación: casi nula en color; TOTAL en elevación

## 2. COLOR

Once negros (TODOS con azul dentro: el canal B siempre supera al R), nueve blancos y un
único azul.

negros:  #000000 #101010 #1C1D1F #202124 #232529 #2E3238 #383E47 #505967 #6F7988
         #8F99A8 #A4ADBA
blancos: #FFFFFF #FAFAFB #F3F4F6 #EDEFF3 #E4E7EC #DEE2E7 #D3D8DF #CAD0D9 #B5BDC9
azules:  #E8F0FF #C8DCFF #94B9FF #709FF5 #538BF3 #266DF0 #245BC2 #1A2233
estado:  #0FC27B (success) #FF5B59 (error) #F5B900 (warning)

LA REGLA DEL AZUL: #266DF0 no pinta NI UN SOLO BOTÓN. Su trabajo entero es:
link-foreground, focus-ring al 30% (#266DF04D), accent-stroke y el borde de 1.08px de la
fila seleccionada. El botón de Attio es NEGRO.

Bordes con alfa, no con token:
  tarjeta: rgba(0,0,0,0.05)      -> se oscurece con lo que haya debajo
  baraja:  rgba(56,62,71,0.1)    -> black-500 al 10%, para que se oscurezcan entre sí

CONTRASTE: #6F7988 (el párrafo del hero) da 4.6:1 — AA EN EL FILO. Attio lo compensa
poniéndolo a 18px/500. NUNCA bajarlo de 16px.

## 3. TIPOGRAFÍA

Reales:      interDisplay / inter / tiemposText / JetBrains Mono
Sustitutas:  Inter Tight  / Inter / Source Serif 4 / JetBrains Mono

DOS DE LAS CUATRO SON IDÉNTICAS: Inter y JetBrains Mono son libres y están en Google
Fonts. Sólo se sustituyen el corte display (interDisplay -> Inter Tight, el corte público
más cercano) y el serif (tiemposText, de Klim Type, comercial -> Source Serif 4).

EL CUERPO PESA 500, NO 400. Cada token trae --text-*--font-weight: 500 de fábrica.
Titulares: 600. Techo: 700. NO hay 800 ni 900.

Tracking: siempre negativo, y MÁS negativo cuanto más grande (-0.005em a 14px,
-0.02em a 64px). Salvo a 12px, donde vuelve a 0: apretar texto diminuto lo hace ilegible.

El titular del hero NO usa la escala. Es fluido respecto a la ALTURA del viewport:
  font-size:      clamp(64px, calc(16px + 5.333svh), 80px)
  letter-spacing: clamp(-2.4px, calc(2.08px - 0.3733svh), -1.28px)
  line-height:    0.95
Cuando svh sube, el tamaño sube y el tracking BAJA. La regla óptica clásica, escrita como
una interpolación lineal en CSS. Y usa svh, no vw: el hero encoge en pantallas bajas para
que los mocks de producto sigan cabiendo debajo.

## 4. CTAs

Geometría compartida: 36px de alto, radio 10px, padding 0 12px, gap 6px, borde de 1px
SIEMPRE presente, label de 14px/500/lh 20px/tracking -0.005em.
Variante grande (hero y móvil): 46px, radio 12px, padding 0 14px, label de 16px.

RADIO 10px NO ES UN TOKEN. La escala es 2/4/6/8/12/16/20. El botón usa un arbitrario que
cae entre lg (8) y xl (12).

LA REGLA DE LOS TRES — una propiedad por variante, y ninguna se repite:
  PRIMARIO: se mueve el DEGRADADO (#202124 plano -> foco #505967 encendido)
  OUTLINE:  se mueve el BORDE     (#CAD0D9 -> #6F7988; salta de la rampa de blancos
                                   a la de negros. El fondo NO cambia)
  GHOST:    se mueve el FONDO     (transparent -> #F3F4F6 -> #EDEFF3)

NINGÚN botón anima transform. No escalan, no se desplazan, no ganan sombra.

LA ASIMETRÍA DEL TIEMPO: entrar 50ms, salir 300ms. Curva cubic-bezier(.2, 0, 0, 1).

El icono cambia el padding, no el layout:
  .button:has(> svg:last-child) { padding-right: 8px; }   /* de 12 a 8 */

focus-visible del primario = su estado de hover + el ring. El teclado ve lo mismo que el
ratón.

## 5. TARJETAS

Una tarjeta es: fondo blanco + borde rgba(0,0,0,.05) + radio 12px + N capas. La variación
entre tarjetas ES LA N.

  shadow-attio-3  ->  tarjeta en reposo
  shadow-attio-4  ->  tarjeta en hover      (las 3 de antes + UNA)
  shadow-attio-5  ->  popover, dropdown
  shadow-attio-6  ->  modal, panel flotante
  shadow-attio-7  ->  reservada

El radio Y el borde cambian con el breakpoint: en móvil es rounded-md (6px) y SIN borde;
a partir de lg (992px) sube a rounded-xl (12px) y aparece el lg:border.

FILA DE PRODUCTO (la única sombra fuera de la escalera):
  radio 15px, borde 1.08px (en móvil 0.54px, la mitad exacta), padding 6.5px/13px
  box-shadow: 0 1px 3px rgba(0,0,0,.04), 0 0 2px rgba(28,40,64,.18)  <- halo AZULADO
  seleccionada: border-color #266DF0

El 1.08px no es un error: Attio escaló la app real y ARRASTRÓ EL BORDE con ella en vez de
redondearlo. NO lo redondees a 1px.

BARAJA: dos rectángulos a 3° y 6°, borde rgba(56,62,71,.1).

PANEL DE CRISTAL: radio 16px, padding 6px con padding-top 0, bg rgba(243,244,246,.8),
backdrop-filter blur(12px). Y EL BLUR SE APAGA DURANTE EL SCROLL
(group-data-hero-scrolling:backdrop-blur-none) porque a 60fps se nota.

## 6. PÍLDORA

DOS elementos, DOS responsabilidades:
  el    -> SÓLO el borde (1px #EDEFF3, radio 13px, :active -> #E4E7EC)
  el 
-> SÓLO el fondo (#FFFFFF, hover -> #FBFBFC) #FBFBFC está ENTRE white-100 y white-200: más sutil que el paso más pequeño de la rampa. Padding asimétrico 5px 7px 5px 11px: 11 al texto, 7 al chevron de 12x12. ## 7. ESPACIADO Base 4px (Tailwind v4). Radios: 2/4/6/8/12/16/20, MÁS tres arbitrarios que se salen a propósito: 10 (botón), 13 (píldora), 15 (fila de la app). BREAKPOINT lg = 992px, NO 1024. Attio lo bajó 32px: es donde una tabla de CRM con la barra lateral deja de caber. Ese punto no lo decide el framework. El header es una SUMA de cuatro piezas que pueden valer 0 (toolbar 36 + nav 68 + subheader 63 + banner 48). Mostrar u ocultar una franja es reasignar una variable. ## 8. NO HACER - No pintes un botón de azul. El botón es NEGRO. - No animes transform en un control. - No uses una sombra de UNA capa. La profundidad se APILA. - No subas la opacidad de una capa. Ninguna pasa del 7%. Añade otra capa. - No uses negro puro (salvo el banner) ni grises neutros: todo lleva azul dentro. - No pongas #6F7988 por debajo de 16px. Está en el filo de AA. - No uses transiciones simétricas. 50ms entrando, 300ms saliendo. - No redondees el 1.08px a 1px. ## ANEXO: STACK Next.js (App Router) en Vercel. Tailwind v4 con @theme, 8 chunks, 462 KB. HTML server-rendered de 1.09 MB. next/font local (woff2, font-display: swap, con familias Fallback y size-adjust). Tres ámbitos de tema: :root, .dark, .blue. Los tokens se compilan a lab() con fallback hex; los hex de este documento son los fallbacks literales del CSS servido. ANTI-BOT: detecta CDP. Con Puppeteer al mando el runtime se cuelga. El CSS y el HTML se sirven sin restricción a curl.