HystoreWiki

10 min de lecturaVer en Markdown

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, ...), enlaces javascript:, vbscript: y data: (excepto data: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, y url() que no sea assets/<nombre>, un archivo https://fonts.gstatic.com/... o una imagen pequeña data:image/...;base64. La clase hystore-required está reservada.
  • Las tags Liquid include, layout y block (usa render, el archivo de layout y hystore_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, no sections/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

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 clase dark en el <html>: da estilo con html:not(.dark) ... o html.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, id en 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 }}.

Liquid
{%- 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 %}:

Liquid
<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 %}
  • section es { 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):
JSON
{
  "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 de config/values.json en sections.hero.settings.

Componentes

components/<nombre>.liquid, usados con {% render 'nombre', clave: valor %}. Un componente solo ve lo que recibe (más las variables globales).

Liquid
{% 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):

Liquid
{% 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).

Liquid
<img src="{{ 'banner.webp' | asset_url }}" alt="{{ store.name }}">
CSS
@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:

HTML
<script src="/sdk/v1.js"></script>
<script src="app.js"></script>
JavaScript
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ágenes data: o las imágenes y fuentes del tema por su dirección completa (https://theme-assets.hystore.com.br/..., como la dan GET /v1/theme/assets y la herramienta list_assets del MCP).
  • app.json es opcional: height de 40 a 2000 píxeles (320 si falta), name para lectores de pantalla, permissions solo customer.
  • 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 check sin 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, /perfil y /entrar (o usan data-hystore-open).