Crea un tema con tu agente
Una referencia para un agente de IA que crea un tema de vista previa de MarsDawn. El agente escribe un único theme.json, lo comprueba y lo renderiza con la herramienta de línea de comandos marsdawn y te da un enlace. Tú lo afinas a ojo en la página Crear un tema y lo envías. Las tablas de campos de abajo se leen del código fuente del kit, y una comprobación las mantiene iguales.
Escribe un theme.json, ejecuta marsdawn theme validate theme.json --require-complete --json hasta que termine con código 0, renderízalo con marsdawn theme preview en claro y en oscuro y después dale un enlace a la persona. Un tema son solo parámetros. Nunca contiene CSS.
Qué es un tema
- Colores para claro y para oscuro: una paleta base y, opcionalmente, colores de sintaxis y de diagramas. Cada uno es
#RRGGBB. - Un diseño de fuente:
sans,seriforounded. - Un conjunto cerrado de opciones de estilo: números dentro de rangos fijos y elecciones de listas fijas. Un color en una opción de estilo es siempre un rol de la paleta, como
accent, nunca un color literal. - Textos visibles: un nombre y un resumen y, opcionalmente, un autor y una licencia.
Una clave que no está en las tablas de abajo es un error, y también lo es una clave escrita dos veces. El archivo pesa como máximo 16 384 bytes.
Comprobar y renderizar
Ejecuta estos comandos en la carpeta que contiene theme.json. Necesitan la herramienta marsdawn: consulta marsdawn para agentes.
marsdawn theme validate theme.json --require-complete --json
marsdawn theme css theme.json --json
marsdawn theme preview theme.json --appearance light -o light.png
marsdawn theme preview theme.json --appearance dark -o dark.png
- Códigos de salida: 0 válido, 1 inválido con identificadores de regla, 2 archivo no encontrado, 64 error de uso.
previewañade 4 cuando el PNG ya existe (pasa--forceo elige otro nombre) y 5 cuando falla el renderizado. - Con
--json, un tema inválido lista sus problemas enissues, cada uno con un identificadorrule, unpathy unmessage. Corrígelos y vuelve a ejecutarlo. --require-completees la comprobación para un tema que se va a publicar: exige además los coloressyntaxydiagramen las dos paletas.cssimprime el CSS que sirve la vista previa.previewrenderiza un documento de ejemplo en un PNG. Ambos validan primero, así que un tema inválido no se renderiza.- Mira el PNG claro y el oscuro antes de entregar el tema.
Partir de un tema integrado
Copia uno de los cuatro temas integrados y cámbialo. Cada uno es un archivo completo y válido:
/assets/theme-sim/kit/themes/dawn.json: el predeterminado y el ejemplo de abajo/assets/theme-sim/kit/themes/classic.json: una paleta más sobria, parecida a la de un documento/assets/theme-sim/kit/themes/modern.json: una paleta más fría y más actual/assets/theme-sim/kit/themes/vivid.json: una paleta más viva y de más contraste
Ejemplo: Dawn
Este es dawn.json, byte por byte. Su única opción de estilo es una línea fina del color de acento para hr.
{
"schemaVersion": 1,
"id": "dawn",
"version": "1.0.0",
"name": { "en": "Dawn" },
"summary": { "en": "Warm Martian sunrise" },
"fontDesign": "sans",
"scenarios": ["agent-review"],
"light": {
"background": "#FFFDFB", "surface": "#F6F0EC", "text": "#26211F", "muted": "#6F6660",
"border": "#EADFD8", "heading": "#26211F", "accent": "#C8471B", "link": "#B03C0C", "quote": "#D97A4A",
"syntax": { "keyword": "#B03C0C", "string": "#2F6F5E", "comment": "#746B66", "number": "#9A5B00", "function": "#5B3E8C", "type": "#1F6FB2" },
"diagram": { "node": "#FBE9E0", "nodeBorder": "#CA6C3C", "text": "#26211F", "line": "#8A6A5C", "secondary": "#E6F1EE", "tertiary": "#FFF6F1", "note": "#FFF1D6" }
},
"dark": {
"background": "#1C1A1F", "surface": "#262229", "text": "#EBE4DF", "muted": "#A39992",
"border": "#3A343A", "heading": "#F4EEEA", "accent": "#FF8A50", "link": "#FF9E6B", "quote": "#E08A5C",
"syntax": { "keyword": "#FF9E6B", "string": "#7FD1B9", "comment": "#938A84", "number": "#F2C572", "function": "#C7A6F5", "type": "#6CB6FF" },
"diagram": { "node": "#3A2A26", "nodeBorder": "#E08A5C", "text": "#EBE4DF", "line": "#B8A69C", "secondary": "#23332F", "tertiary": "#2B2427", "note": "#3D3322" }
},
"style": {
"hr": { "style": { "type": "line", "color": "accent", "thickness": 1 } }
}
}
Campos
Cada objeto de abajo se lee de la misma manera. required significa que la clave debe estar presente, y una clave que no figura en la lista es un error.
Tipos: string, integer, number, boolean, map (etiqueta de idioma a texto), enum (uno de los valores listados), role (un rol de la paleta) y object (la tabla a la que enlaza). Un [] después de un tipo significa una lista.
theme.json
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
schemaVersion | integer | required | Siempre 1. |
id | string | required | De 1 a 32 caracteres: letras minúsculas y dígitos, en tramos unidos por guiones simples, como calm-green. |
version | string | required | MAJOR.MINOR.PATCH, como 1.0.0. |
name | map | required | Etiqueta de idioma a texto. en es obligatoria; las demás claves son etiquetas de idioma como zh-Hant. Cada texto tiene como máximo 48 caracteres, sin enlaces ni saltos de línea. |
summary | map | required | La misma forma que name, con un máximo de 120 caracteres por texto. |
fontDesign | enum | required | sans, serif, rounded |
scenarios | enum[] | required | agent-review, technical-docs, formal-output, notes-sharing — Uno o dos, sin repetir. Fijan cuánto contraste debe alcanzar el tema. |
author | object | optional | author |
license | string | optional | Un identificador SPDX, como Apache-2.0. Los temas enviados se publican bajo Apache-2.0. |
light | object | required | light, dark |
dark | object | required | light, dark |
style | object | optional | style |
author
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
name | string | required | Como máximo 64 caracteres, sin enlaces. |
github | string | optional | Un usuario de GitHub. En un envío, pon el de quien lo envía. |
light, dark
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
background | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
surface | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
text | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
muted | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
border | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
heading | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
accent | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
link | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
quote | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
syntax | object | optional (obligatorio para publicar, con --require-complete) | syntax |
diagram | object | optional (obligatorio para publicar, con --require-complete) | diagram |
syntax
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
keyword | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
string | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
comment | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
number | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
function | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
type | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
diagram
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
node | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
nodeBorder | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
text | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
line | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
secondary | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
tertiary | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
note | string | required | # y seis dígitos hexadecimales, como #1C1C1C. |
style
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
bodySize | number | optional | rango |
lineHeight | number | optional | rango |
headingWeight | number | optional | rango |
radius | number | optional | rango |
maxWidth | number | optional | rango |
h1 | object | optional | style.h1 |
h2 | object | optional | style.h2 |
blockquote | object | optional | style.blockquote |
hr | object | optional | style.hr |
table | object | optional | style.table |
listMarker | role | optional | un rol de la paleta |
inlineCode | role | optional | un rol de la paleta — Solo text o un rol de sintaxis: keyword, string, comment, number, function o type. |
link | object | optional | style.link |
syntax | object | optional | style.syntax |
style.h1
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
size | number | optional | rango |
letterSpacing | number | optional | rango |
align | enum | optional | left, center |
decoration | object | optional | type |
style.h2
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
letterSpacing | number | optional | rango |
decoration | object | optional | type |
italic | boolean | optional | true, false |
style.blockquote
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
style | object | optional | type |
italic | boolean | optional | true, false |
style.hr
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
style | object | optional | type |
style.table
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
header | object | optional | type |
verticalRules | boolean | optional | true, false |
rounded | boolean | optional | true, false |
style.link
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
underline | boolean | optional | true, false |
style.syntax
| Campo | Tipo | Presencia | Valores y notas |
|---|---|---|---|
boldKeywords | boolean | optional | true, false |
Opciones con un tipo
Estas opciones de estilo son objetos con un type. Cada type admite los campos listados para él y ningún otro.
| Opción | type | Campo | Presencia | Tipo |
|---|---|---|---|---|
style.h1.decoration | rule | — | — | — |
style.h1.decoration | none | — | — | — |
style.h1.decoration | shortRule | color | required | role |
style.h1.decoration | gradientBar | from | required | role |
style.h1.decoration | gradientBar | to | required | role |
style.h2.decoration | rule | — | — | — |
style.h2.decoration | none | — | — | — |
style.h2.decoration | dot | color | required | role |
style.blockquote.style | bar | width | optional | number rango |
style.blockquote.style | panel | — | — | — |
style.hr.style | line | color | optional | role |
style.hr.style | line | thickness | optional | number rango |
style.hr.style | shortCentered | color | required | role |
style.hr.style | gradient | colors | required | role[] Dos o tres roles. |
style.table.header | surface | — | — | — |
style.table.header | accentRule | color | required | role |
style.table.header | filled | background | required | role |
style.table.header | filled | text | required | role |
style.table.header | filled | border | optional | role |
Roles de la paleta
Un rol de la paleta es uno de estos nombres:
background, surface, text, muted, border, heading, accent, link, quote, keyword, string, comment, number, function, type
Números
Un número se ajusta al múltiplo más cercano de su paso y después debe estar entre el mínimo y el máximo. Si no, la regla es number.range.
| Campo | Mín. | Máx. | Paso |
|---|---|---|---|
style.bodySize | 14 | 18 | 1 |
style.lineHeight | 1.4 | 1.9 | 0.05 |
style.headingWeight | 400 | 900 | 50 |
style.radius | 0 | 16 | 1 |
style.maxWidth | 600 | 1000 | 1 |
style.h1.size | 1.8 | 2.4 | 0.1 |
style.h1.letterSpacing | -0.03 | 0.03 | 0.005 |
style.h2.letterSpacing | -0.03 | 0.03 | 0.005 |
style.blockquote.style.width | 2 | 4 | 1 |
style.hr.style.thickness | 1 | 4 | 1 |
Qué más se comprueba
- Contraste. Cada par de colores que dibuja la vista previa, como
textsobrebackground, debe alcanzar 4,5:1 para el texto y 3:1 para las líneas y lo demás que no es texto. Los escenarios que indicas suben algunos colores a 4,5:1. El mensaje nombra el par, su proporción y la que necesita. - Textos. Se rechaza un nombre, un resumen o un nombre de autor que esté vacío, sea demasiado largo o contenga un enlace, un carácter de control o un carácter de formato invisible o bidireccional.
- Identificadores de regla. Cada problema lleva uno, como
contrast.pair,color.hex,number.range,id.pattern,schema.unknownKeyopalette.incomplete. Corrige el archivo según lo que dicen el identificador y elpath.
Entrégalo como un enlace
La página Crear un tema abre un tema desde un enlace, para que la persona pueda afinarlo a ojo. El enlace es la dirección de esa página, luego #theme= y luego los bytes del archivo en base64url sin relleno. El archivo puede pesar como máximo 16 384 bytes. Esta única línea construye el enlace en zsh y en bash, en macOS y en Linux:
printf 'https://marsdawn.southern-light.dev/es/themes/new/#theme=%s\n' "$(base64 < theme.json | tr -d '\n=' | tr '+/' '-_')"
El tema viaja detrás de un #, que el navegador no envía al sitio, y el sitio no lo envía a ninguna parte. Eso sí, el enlace queda en el historial de tu shell y en el del navegador, y cualquiera a quien se lo pegues puede leer el tema.
Envíalo
El agente prepara el archivo y el enlace. La persona lo envía. Las dos vías piden el Developer Certificate of Origin y la publicación bajo Apache-2.0, y esa certificación la hace la persona: el agente no marca la casilla ni firma por ella.
- Issue. La persona abre el enlace y pulsa Submit en la página «Crear un tema», o abre el formulario de issue y pega el JSON en Theme. Un workflow comprueba el tema y responde.
- Pull request. Cambia solo
themes/<id>/theme.json, pon enauthor.githubel usuario del autor de la pull request y firma cada commit congit commit -s. Las reglas están en CONTRIBUTING.md.
El nombre del autor, el nombre, el resumen y el usuario de GitHub de un tema enviado pasan a ser públicos de forma permanente, así que pon en author.name un nombre que la persona esté contenta de publicar.
Temas publicados
Para ver cómo son los temas publicados, explora la galería de temas.