Guía del tema en código de Hystore
Un tema en código de Hystore es una carpeta de archivos Liquid, CSS y JSON que dibuja la vitrina de la tienda: cabecera, pie, inicio, tienda, páginas de producto, noticias, jugadores. El código de la propia tienda se encarga del carrito, el pago, la cuenta y el inicio de sesión, que nunca cargan el tema.
Esta guía es la referencia para personas y asistentes de IA que editan un tema. Todo aquí lo revisan los servidores de Hystore: un archivo que rompe una regla se rechaza al guardar, con el archivo, la línea y el motivo.
Flujo de trabajo
npx @hystudios/hystore login # aprueba en el panel (sin token para pegar)
npx @hystudios/hystore theme pull # descarga el borrador a esta carpeta
npx @hystudios/hystore theme dev # sigue la carpeta, envía cada guardado, vista previa en vivo
npx @hystudios/hystore theme check # revisa en tu máquina, sin enviar
npx @hystudios/hystore theme push # envía los cambios al borrador
npx @hystudios/hystore theme publish # pide publicar; quien tiene permiso aprueba en el panel
npx @hystudios/hystore theme versions # las últimas 30 versiones publicadas
npx @hystudios/hystore theme rollback 3 # trae la versión 3 de vuelta al borrador
npx @hystudios/hystore theme new # empieza una carpeta a partir del tema Base de Hystore
- Las ediciones siempre van al borrador. La tienda en el aire solo cambia cuando se aprueba un pedido de publicación en el panel (Personalización > Tema en código).
- El enlace de vista previa (de
theme dev) muestra el borrador en la dirección real de la tienda durante 24 horas y se recarga solo tras cada envío. - Si alguien cambió un archivo en el panel después de tu último pull, el envío de ese archivo se rechaza como conflicto: haz pull, une y envía de nuevo.
Reglas fijas
Esto se rechaza (el archivo no se guarda):
<script>,<style>,<iframe>,<form>,<input>,<textarea>,<select>,<object>,<embed>,<base>,<meta>,<link>,<foreignObject>de SVG.- Atributos de evento (
onclick,onload, ...), enlacesjavascript:,vbscript:ydata:(exceptodata:image/png|jpeg|gif|webp;base64),srcdoc,http-equiv, referencias numéricas de carácter (&#...;): escribe el carácter mismo. - En CSS:
@import,expression(),behavior,-moz-binding,@namespace,@document,image-set(), escapes con barra invertida fuera de strings, yurl()que no seaassets/<nombre>, un archivohttps://fonts.gstatic.com/...o una imagen pequeñadata:image/...;base64. La clasehystore-requiredestá reservada. - Las tags Liquid
include,layoutyblock(usarender, el archivo de layout yhystore_block).
No hay JavaScript en las páginas del tema. La interacción viene de atributos
data-hystore-* y de elementos <hystore-*> (mira Piezas interactivas). La
tienda limpia de nuevo el HTML dibujado, y su política de seguridad de
contenido bloquea scripts. Lo que necesita JavaScript va en una app aislada
(mira Apps).
Un tema solo lee datos públicos de la tienda y el nick y la skin de quien
mira. Nunca ve saldos, correos, pedidos ni pagos, y no cambia precios: la
compra pasa por data-hystore-add-to-cart, que maneja la tienda.
Carpeta
theme.json nombre, modos de color, configuración general (obligatorio)
layout/theme.liquid marco de todas las páginas; debe mostrar {{ content }} (obligatorio)
pages/<página>.liquid un archivo por página (pages/home.liquid es obligatorio)
templates/<página>.json secciones de una página, en orden, editables en "Personalizar tema"
sections/<nombre>.liquid bloques con un {% schema %}, usados por templates o {% section %}
components/<nombre>.liquid piezas usadas con {% render 'nombre' %}
styles/<nombre>.css se cargan todos los archivos, en orden de nombre
locales/pt.json en.json es.json textos del filtro | t
config/values.json valores elegidos en "Personalizar tema"
assets/<nombre>.<ext> imágenes (png, jpg, webp, gif, avif hasta 3 MB) y fuentes (woff, woff2 hasta 1 MB)
apps/<app>/<archivo> apps aisladas con JavaScript: html, js, css, json (mira Apps)
- Nombres: letras minúsculas, números,
-y_, un nivel dentro de cada carpeta (sections/hero.liquid, nosections/home/hero.liquid); las apps usan dos (apps/ruleta/app.js). - Límites: 400 archivos de texto, 256 KB por archivo, 5 MB de texto; 100
archivos de medios, 40 MB. Los archivos SVG no entran como medios (
<svg>directo en el Liquid sí). - Páginas:
home,shop,category,product,search,news,post,page,players,404,maintenance. Una página que el tema no tiene la dibuja el diseño clásico de la tienda.
| Página | Dirección | Objetos extra |
|---|---|---|
| home | / |
|
| shop | /loja, /loja/<servidor> |
categories, server (con servidor) |
| category | /loja/<servidor>/<categoría> |
category, products (48) |
| product | /loja/<servidor>/<categoría>/produto/<slug> |
product |
| search | /busca?q=... |
search.query, search.results (48 productos) |
| news | /noticias |
|
| post | /noticias/<slug> |
post |
| page | /p/<slug> |
page.slug |
| players | /jogadores |
|
| 404, maintenance |
Carrito (/carrinho), pago, perfil (/perfil), compras, contraseña e inicio
de sesión (/entrar) son páginas de la propia tienda: enlázalas, nunca las
rehagas.
theme.json
{
"name": "Mi tema",
"version": "1.0.0",
"author": "Yo",
"color_modes": ["dark", "light"],
"settings": [
{ "id": "accent", "type": "color", "label": "Color de acento", "default": "" },
{ "id": "radius", "type": "range", "label": "Redondeo", "min": 0, "max": 28, "default": 14 },
{ "id": "show_search", "type": "checkbox", "label": "Búsqueda en la cabecera", "default": true }
]
}
color_modes: los modos que dibuja el tema (dark,light); oscuro si falta. En modo claro la página no tiene la clasedarken el<html>: da estilo conhtml:not(.dark) ...ohtml.dark ....- Tipos de configuración:
text,textarea,richtext,image,url,color,number,range(min,max,step),checkbox,select(options: textos o{ "value", "label" }),product,category,server,list(líneas de texto). Hasta 60 configuraciones,iden minúsculas. - La configuración general es
{{ settings.<id> }}en cualquier lugar, y los colores y números también son variables CSS--theme-<id>en la raíz del tema. - El color primario de la tienda es
hsl(var(--primary))(texto sobre él:hsl(var(--primary-foreground))).
config/values.json guarda lo que eligió el dueño de la tienda:
{ "settings": { "accent": "#ff5500" } }. Mantén ese formato al editarlo.
Layout y páginas
layout/theme.liquid envuelve todas las páginas y debe tener {{ content }}
donde va la página. La tienda agrega un área obligatoria después del tema
(anuncios en los planes gratuitos, enlaces legales); no se puede ocultar ni
estilizar.
Una página es pages/<página>.liquid, templates/<página>.json o ambos. Con
ambos, el archivo de la página coloca las secciones del template con
{{ template_sections }}.
{%- comment -%} pages/home.liquid {%- endcomment -%}
<div class="intro">{{ 'home.welcome' | t }}</div>
{{ template_sections }}
Secciones
Una sección es sections/<nombre>.liquid, que termina con un
{% schema %}:
<section class="hero">
<h1>{{ section.settings.title | default: store.name }}</h1>
{%- for block in section.blocks -%}
<p>{{ block.settings.text }}</p>
{%- endfor -%}
</section>
{% schema %}
{
"name": "Destacado",
"settings": [
{ "id": "title", "type": "text", "label": "Título", "default": "" }
],
"blocks": [
{ "type": "line", "name": "Línea", "settings": [{ "id": "text", "type": "text", "label": "Texto" }] }
]
}
{% endschema %}
sectiones{ id, type, settings, blocks }; la configuración viene con sus valores por defecto. Una sección ve lo que ve su página (product, datos cargados...).- Un template lista secciones (como máximo 40 dibujadas por página):
{
"sections": {
"hero": { "type": "hero", "settings": { "title": "Bienvenido" } },
"news": { "type": "latest-news", "disabled": true }
},
"order": ["hero", "news"]
}
type es el nombre del archivo en sections/; "disabled": true la oculta.
{% section 'hero' %}dibuja una sección en un lugar fijo (layout, página); los valores vienen deconfig/values.jsonensections.hero.settings.
Componentes
components/<nombre>.liquid, usados con {% render 'nombre', clave: valor %}.
Un componente solo ve lo que recibe (más las variables globales).
{% render 'product-card', product: item %}
Textos en tres idiomas
locales/pt.json, en.json y es.json guardan textos (se permiten objetos
anidados). {{ 'nav.shop' | t }} muestra el texto en el idioma de quien
visita, cayendo en el portugués y después en la propia clave. Un archivo de
idioma que falta es un aviso.
Datos
En todas partes:
| Objeto | Campos |
|---|---|
store |
id, name, slogan, description, logo, ip, type (game o digital), game (minecraft, hytale o null), currency |
route |
path, page, params (server_id, category_slug, product_slug, slug), query.q |
modules |
forum, wiki, support, forms, launcher, discord, vip (true cuando está activo) |
customer |
logged_in, nick, skin_url |
cart |
count |
settings |
configuración general del theme.json |
locale |
pt, en o es |
color_mode |
dark o light |
Más datos, cuando hagan falta, con {% load %} (como máximo 24 por página; la
misma carga se busca una sola vez):
{% load products, limit: 8, featured: true as featured %}
{% for p in featured %}{% render 'product-card', product: p %}{% endfor %}
| Load | Opciones | Trae |
|---|---|---|
products |
limit (12), category, server, featured, query |
lista de productos |
product |
slug, server |
un producto o null |
categories |
server |
lista de categorías |
servers |
lista de { id, name, url, image, online } |
|
server_status |
{ online, players_online, players_max, version } o null |
|
news |
limit (6), page |
lista de posts |
post |
slug |
un post o null |
ranking |
period (month o all), limit (5) |
lista de { position, nick, total_spent, orders, skin_url } |
top_supporter |
{ nick, total_spent, purchases, skin_url } o null |
|
goal |
{ target, current, percent, ends_at, rewards } o null |
|
discord |
{ url, server_id, widget } |
|
launcher |
{ name, version, downloads } o null |
|
socials |
mapa instagram, discord, twitter, tiktok, telegram, whatsapp → URL |
|
online_players |
{ total, servers: [{ name, count, max, players }] } |
- Producto:
id,name,slug,url,price,original_price,on_sale,image,short_description,description(HTML),duration_days,min_quantity,featured,sold_out,digital,category_slug,server_id. - Categoría:
id,name,slug,url,description,image,product_count. - Post:
id,title,slug,url,author,image,excerpt,content(HTML),published_at.
Filtros
De Hystore:
t: texto en el idioma de quien visita,{{ 'cart.add' | t }}.money: precio en la moneda de la tienda,{{ product.price | money }}.asset_url: dirección de un medio enviado,{{ 'logo.webp' | asset_url }}.skin_url: cabeza de Minecraft de un nick,{{ customer.nick | skin_url: 64 }}.raw: solo para HTML que la tienda ya limpió (product.description,post.content). Todo lo demás se escapa.
De Liquid: abs, append, at_least, at_most, capitalize, ceil,
compact, concat, date, default, divided_by, downcase, escape,
escape_once, first, floor, join, json, last, lstrip, map,
minus, modulo, newline_to_br, plus, prepend, remove,
remove_first, replace, replace_first, reverse, round, rstrip,
size, slice, sort, sort_natural, split, strip, strip_html,
strip_newlines, sum, times, truncate, truncatewords, uniq,
upcase, url_decode, url_encode, where.
Tags: if/elsif/else, unless, case/when, for (break,
continue), assign, capture, echo, liquid, comment, raw,
cycle, increment, decrement, tablerow, más render, section,
load, hystore_block, schema.
Bloques listos
{% hystore_block 'nombre', clave: valor %} coloca una pieza de la tienda que
necesita datos en vivo o la lógica de la tienda:
heading, image, button, icon, video, card, testimonial,
countdown, banner, spacer, divider, server-hero, products,
featured-product, categories, servers, ranking, top-supporter,
goal, news, discord, launcher, server-status, server-ip,
socials, logo, menu, account-actions, language, color-mode,
legal-links, copyright.
El nombre va entre comillas. Las opciones son texto, números o true/false.
Piezas interactivas
Atributos (la tienda hace el trabajo, sin JavaScript en el tema):
| Atributo | Qué hace |
|---|---|
data-hystore-add-to-cart="<id del producto>" |
agrega el producto al carrito (pide iniciar sesión antes) |
data-hystore-open="cart|account|login|search" |
abre ese lugar |
data-hystore-copy="texto" |
copia el texto (IP del servidor...) |
data-hystore-color-mode="toggle|dark|light" |
cambia el modo de color |
data-hystore-language="pt|en|es" |
cambia el idioma |
data-hystore-cart-count |
el elemento siempre muestra la cantidad del carrito |
data-hystore-tabs |
los botones [data-hystore-tab="x"] muestran [data-hystore-panel="x"] |
data-hystore-accordion (="single" deja uno abierto) |
cada [data-hystore-accordion-item] tiene un [data-hystore-accordion-toggle] y un [data-hystore-accordion-panel] |
data-hystore-modal="id" |
abre #id, que tiene data-hystore-modal-panel; data-hystore-modal-close cierra |
data-hystore-carousel |
[data-hystore-slides] se desplaza, [data-hystore-prev]/[data-hystore-next], data-hystore-autoplay="5000" |
data-hystore-countdown="2026-12-24T23:59:00Z" |
llena [data-hystore-days], [data-hystore-hours], [data-hystore-minutes], [data-hystore-seconds] (o su propio texto) |
data-hystore-animate |
recibe data-visible cuando aparece en pantalla (anímalo en CSS) |
Elementos que llena la tienda: <hystore-cart-count>, <hystore-account>
(iniciar sesión o el menú de la cuenta; con data-cart muestra también el
botón del carrito de la tienda), <hystore-server-status>,
<hystore-online-players>, <hystore-search data-placeholder="...">,
<hystore-skin data-nick="Steve" data-size="128">,
<hystore-video data-youtube="<id o enlace>">,
<hystore-app data-app="<nombre>"> (una app aislada, mira Apps).
Los enlaces dentro de la tienda (<a href="/loja">) cambian de página sin
recargar; las páginas de cuenta y pago siempre abren como página nueva.
Imágenes y fuentes
Ponlas en assets/ (la herramienta las envía; el servidor revisa los bytes).
<img src="{{ 'banner.webp' | asset_url }}" alt="{{ store.name }}">
@font-face { font-family: "Display"; src: url(assets/display.woff2) format("woff2"); font-display: swap; }
.hero { background-image: url(assets/banner.webp); }
Apps
Una app es una pequeña página con JavaScript (calculadora, ruleta de premios,
minijuego, animación) que el tema coloca con
<hystore-app data-app="ruleta"></hystore-app>. Vive en apps/<app>/ y
empieza por index.html:
apps/ruleta/index.html la página (obligatoria)
apps/ruleta/app.js el script
apps/ruleta/app.css el estilo
apps/ruleta/app.json { "name": "Ruleta de premios", "height": 420, "permissions": ["customer"] }
Corre aislada, en un marco en otro dominio (hystoreapps.cloud): sin red
(fetch, XHR y WebSocket bloqueados), sin cookies ni almacenamiento, sin
formularios, sin ventanas nuevas, y no puede cambiar ni navegar la página de la
tienda. Habla con la tienda solo por el puente:
<script src="/sdk/v1.js"></script>
<script src="app.js"></script>
hystore.products({ category: 'vips', limit: 6 }).then(function (products) {
// [{ id, name, url, price, priceText, originalPrice, onSale, image, shortDescription, ... }]
});
hystore.categories(); // categorías activas
hystore.addToCart(productId, 1); // 'added', o 'login' (la tienda pide iniciar sesión)
hystore.navigate('/loja'); // una página de la tienda
hystore.open('cart'); // 'cart' | 'login' | 'account' | 'search'
hystore.context(); // { locale, colorMode, store: { name, currency } }
hystore.customer(); // { loggedIn, nick, skinUrl }, solo con "customer" en app.json
hystore.resize(600); // o hystore.autosize() para seguir la página
- Todo es dato público: el precio viene de la tienda, y el carrito recibe solo el id del producto. Las páginas de dinero y de cuenta nunca muestran apps.
- Carga solo los archivos de la propia app y
/sdk/v1.js; scripts y estilos de otros sitios, marcos y formularios se rechazan al guardar. Las imágenes pueden ser imágenesdata:o las imágenes y fuentes del tema por su dirección completa (https://theme-assets.hystore.com.br/..., como la danGET /v1/theme/assetsy la herramientalist_assetsdel MCP). app.jsones opcional:heightde 40 a 2000 píxeles (320 si falta),namepara lectores de pantalla,permissionssolocustomer.- Límites: 10 apps por tema, 20 archivos y 512 KB por app.
- Los mensajes de más se ignoran; agregar al carrito y cambiar de página tienen límites propios, pequeños.
Antes de publicar
npx @hystudios/hystore theme checksin errores.- Todo texto que lee el visitante pasa por
| t, en pt, en y es. - El tema funciona en los modos de color que declara y en el celular.
- Los enlaces de carrito, cuenta e inicio de sesión apuntan a
/carrinho,/perfily/entrar(o usandata-hystore-open).
