Guia do tema em código da Hystore
Um tema em código da Hystore é uma pasta de arquivos Liquid, CSS e JSON que desenha a vitrine da loja: cabeçalho, rodapé, início, loja, páginas de produto, notícias, jogadores. O código da própria loja cuida do carrinho, do pagamento, da conta e do login, que nunca carregam o tema.
Este guia é a referência para pessoas e para assistentes de IA que editam um tema. Tudo aqui é conferido pelos servidores da Hystore: um arquivo que quebra uma regra é recusado ao salvar, com o arquivo, a linha e o motivo.
Fluxo de trabalho
npx @hystudios/hystore login # aprove no painel (sem token para colar)
npx @hystudios/hystore theme pull # baixe o rascunho para esta pasta
npx @hystudios/hystore theme dev # acompanhe a pasta, envie a cada gravação, prévia ao vivo
npx @hystudios/hystore theme check # confira na sua máquina, sem enviar
npx @hystudios/hystore theme push # envie as mudanças para o rascunho
npx @hystudios/hystore theme publish # peça a publicação; quem tem permissão aprova no painel
npx @hystudios/hystore theme versions # as últimas 30 versões publicadas
npx @hystudios/hystore theme rollback 3 # traga a versão 3 de volta para o rascunho
npx @hystudios/hystore theme new # comece uma pasta a partir do tema Base da Hystore
- As edições vão sempre para o rascunho. A loja no ar só muda quando um pedido de publicação é aprovado no painel (Personalização > Tema em código).
- O link de prévia (do
theme dev) mostra o rascunho no endereço real da loja por 24 horas e recarrega sozinho a cada envio. - Se alguém mudou um arquivo no painel depois do seu último pull, o envio desse arquivo é recusado como conflito: faça o pull, junte e envie de novo.
Regras fixas
Isto é recusado (o arquivo não é salvo):
<script>,<style>,<iframe>,<form>,<input>,<textarea>,<select>,<object>,<embed>,<base>,<meta>,<link>,<foreignObject>de SVG.- Atributos de evento (
onclick,onload, ...), linksjavascript:,vbscript:edata:(excetodata:image/png|jpeg|gif|webp;base64),srcdoc,http-equiv, referências numéricas de caractere (&#...;): escreva o próprio caractere. - No CSS:
@import,expression(),behavior,-moz-binding,@namespace,@document,image-set(), escapes com barra invertida fora de strings, eurl()que não sejaassets/<nome>, um arquivohttps://fonts.gstatic.com/...ou uma imagem pequenadata:image/...;base64. A classehystore-requiredé reservada. - As tags Liquid
include,layouteblock(userender, o arquivo de layout ehystore_block).
Não existe JavaScript nas páginas do tema. A interação vem de atributos
data-hystore-* e de elementos <hystore-*> (veja Peças interativas). A loja
limpa o HTML desenhado de novo, e a política de segurança de conteúdo bloqueia
scripts. O que precisa de JavaScript vai num app isolado (veja Apps).
Um tema só lê dados públicos da loja e o nick e a skin de quem está olhando.
Ele nunca vê saldo, e-mail, pedido ou pagamento, e não muda preços: a compra
passa por data-hystore-add-to-cart, tratado pela loja.
Pasta
theme.json nome, modos de cor, configurações gerais (obrigatório)
layout/theme.liquid moldura de todas as páginas; precisa mostrar {{ content }} (obrigatório)
pages/<página>.liquid um arquivo por página (pages/home.liquid é obrigatório)
templates/<página>.json seções de uma página, em ordem, editáveis em "Personalizar tema"
sections/<nome>.liquid blocos com um {% schema %}, usados por templates ou {% section %}
components/<nome>.liquid peças usadas com {% render 'nome' %}
styles/<nome>.css todos os arquivos são carregados, em ordem de nome
locales/pt.json en.json es.json textos do filtro | t
config/values.json valores escolhidos em "Personalizar tema"
assets/<nome>.<ext> imagens (png, jpg, webp, gif, avif até 3 MB) e fontes (woff, woff2 até 1 MB)
apps/<app>/<arquivo> apps isolados com JavaScript: html, js, css, json (veja Apps)
- Nomes: letras minúsculas, números,
-e_, um nível dentro de cada pasta (sections/hero.liquid, nãosections/home/hero.liquid); apps usam dois (apps/roleta/app.js). - Limites: 400 arquivos de texto, 256 KB por arquivo, 5 MB de texto; 100
arquivos de mídia, 40 MB. Arquivos SVG não entram como mídia (
<svg>direto no Liquid pode). - Páginas:
home,shop,category,product,search,news,post,page,players,404,maintenance. Uma página que o tema não tem é desenhada pelo layout clássico da loja.
| Página | Endereço | Objetos a mais |
|---|---|---|
| home | / |
|
| shop | /loja, /loja/<servidor> |
categories, server (com servidor) |
| category | /loja/<servidor>/<categoria> |
category, products (48) |
| product | /loja/<servidor>/<categoria>/produto/<slug> |
product |
| search | /busca?q=... |
search.query, search.results (48 produtos) |
| news | /noticias |
|
| post | /noticias/<slug> |
post |
| page | /p/<slug> |
page.slug |
| players | /jogadores |
|
| 404, maintenance |
Carrinho (/carrinho), pagamento, perfil (/perfil), compras, senha e
entrar (/entrar) são páginas da própria loja: aponte links para elas, nunca
as refaça.
theme.json
{
"name": "Meu tema",
"version": "1.0.0",
"author": "Eu",
"color_modes": ["dark", "light"],
"settings": [
{ "id": "accent", "type": "color", "label": "Cor de destaque", "default": "" },
{ "id": "radius", "type": "range", "label": "Arredondamento", "min": 0, "max": 28, "default": 14 },
{ "id": "show_search", "type": "checkbox", "label": "Busca no cabeçalho", "default": true }
]
}
color_modes: os modos que o tema desenha (dark,light); escuro quando ausente. No modo claro a página não tem a classedarkno<html>: estilize comhtml:not(.dark) ...ouhtml.dark ....- Tipos de configuração:
text,textarea,richtext,image,url,color,number,range(min,max,step),checkbox,select(options: textos ou{ "value", "label" }),product,category,server,list(linhas de texto). Até 60 configurações,idem minúsculas. - As configurações gerais são
{{ settings.<id> }}em qualquer lugar, e cores e números também viram variáveis CSS--theme-<id>na raiz do tema. - A cor primária da loja é
hsl(var(--primary))(texto sobre ela:hsl(var(--primary-foreground))).
config/values.json guarda o que o dono da loja escolheu:
{ "settings": { "accent": "#ff5500" } }. Mantenha esse formato ao editá-lo.
Layout e páginas
layout/theme.liquid envolve todas as páginas e precisa ter {{ content }}
onde a página entra. A loja põe uma área obrigatória depois do tema (anúncios
nos planos gratuitos, links legais); ela não pode ser escondida nem
estilizada.
Uma página é pages/<página>.liquid, templates/<página>.json ou os dois.
Com os dois, o arquivo da página põe as seções do template com
{{ template_sections }}.
{%- comment -%} pages/home.liquid {%- endcomment -%}
<div class="intro">{{ 'home.welcome' | t }}</div>
{{ template_sections }}
Seções
Uma seção é sections/<nome>.liquid, terminando com um {% 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": "Destaque",
"settings": [
{ "id": "title", "type": "text", "label": "Título", "default": "" }
],
"blocks": [
{ "type": "line", "name": "Linha", "settings": [{ "id": "text", "type": "text", "label": "Texto" }] }
]
}
{% endschema %}
sectioné{ id, type, settings, blocks }; as configurações vêm com os padrões. Uma seção vê o que a página dela vê (product, dados carregados...).- Um template lista seções (no máximo 40 desenhadas por página):
{
"sections": {
"hero": { "type": "hero", "settings": { "title": "Bem-vindo" } },
"news": { "type": "latest-news", "disabled": true }
},
"order": ["hero", "news"]
}
type é o nome do arquivo em sections/; "disabled": true esconde.
{% section 'hero' %}desenha uma seção num lugar fixo (layout, página); os valores vêm deconfig/values.jsonemsections.hero.settings.
Componentes
components/<nome>.liquid, usados com {% render 'nome', chave: valor %}. Um
componente vê só o que recebe (mais as variáveis globais).
{% render 'product-card', product: item %}
Textos em três línguas
locales/pt.json, en.json e es.json guardam textos (objetos aninhados
podem). {{ 'nav.shop' | t }} mostra o texto na língua de quem visita, caindo
no português e depois na própria chave. Um arquivo de língua faltando é um
aviso.
Dados
Em todo lugar:
| Objeto | Campos |
|---|---|
store |
id, name, slogan, description, logo, ip, type (game ou digital), game (minecraft, hytale ou null), currency |
route |
path, page, params (server_id, category_slug, product_slug, slug), query.q |
modules |
forum, wiki, support, forms, launcher, discord, vip (true quando ligado) |
customer |
logged_in, nick, skin_url |
cart |
count |
settings |
configurações gerais do theme.json |
locale |
pt, en ou es |
color_mode |
dark ou light |
Mais dados, quando precisar, com {% load %} (no máximo 24 por página; o
mesmo carregamento é buscado uma vez só):
{% load products, limit: 8, featured: true as featured %}
{% for p in featured %}{% render 'product-card', product: p %}{% endfor %}
| Load | Opções | Traz |
|---|---|---|
products |
limit (12), category, server, featured, query |
lista de produtos |
product |
slug, server |
um produto ou null |
categories |
server |
lista de categorias |
servers |
lista de { id, name, url, image, online } |
|
server_status |
{ online, players_online, players_max, version } ou null |
|
news |
limit (6), page |
lista de posts |
post |
slug |
um post ou null |
ranking |
period (month ou all), limit (5) |
lista de { position, nick, total_spent, orders, skin_url } |
top_supporter |
{ nick, total_spent, purchases, skin_url } ou null |
|
goal |
{ target, current, percent, ends_at, rewards } ou null |
|
discord |
{ url, server_id, widget } |
|
launcher |
{ name, version, downloads } ou null |
|
socials |
mapa instagram, discord, twitter, tiktok, telegram, whatsapp → URL |
|
online_players |
{ total, servers: [{ name, count, max, players }] } |
- Produto:
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. - Categoria:
id,name,slug,url,description,image,product_count. - Post:
id,title,slug,url,author,image,excerpt,content(HTML),published_at.
Filtros
Da Hystore:
t: texto na língua de quem visita,{{ 'cart.add' | t }}.money: preço na moeda da loja,{{ product.price | money }}.asset_url: endereço de uma mídia enviada,{{ 'logo.webp' | asset_url }}.skin_url: cabeça do Minecraft de um nick,{{ customer.nick | skin_url: 64 }}.raw: só para HTML que a loja já limpou (product.description,post.content). Todo o resto é escapado.
Do 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, mais render, section, load,
hystore_block, schema.
Blocos prontos
{% hystore_block 'nome', chave: valor %} põe uma peça da loja que precisa
de dados ao vivo ou da lógica da loja:
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.
O nome vai entre aspas. As opções são texto, números ou true/false.
Peças interativas
Atributos (a loja faz o trabalho, sem JavaScript no tema):
| Atributo | O que faz |
|---|---|
data-hystore-add-to-cart="<id do produto>" |
põe o produto no carrinho (pede login antes) |
data-hystore-open="cart|account|login|search" |
abre esse lugar |
data-hystore-copy="texto" |
copia o texto (IP do servidor...) |
data-hystore-color-mode="toggle|dark|light" |
troca o modo de cor |
data-hystore-language="pt|en|es" |
troca a língua |
data-hystore-cart-count |
o elemento sempre mostra a quantidade do carrinho |
data-hystore-tabs |
botões [data-hystore-tab="x"] mostram [data-hystore-panel="x"] |
data-hystore-accordion (="single" deixa um aberto) |
cada [data-hystore-accordion-item] tem um [data-hystore-accordion-toggle] e um [data-hystore-accordion-panel] |
data-hystore-modal="id" |
abre #id, que tem data-hystore-modal-panel; data-hystore-modal-close fecha |
data-hystore-carousel |
[data-hystore-slides] rola, [data-hystore-prev]/[data-hystore-next], data-hystore-autoplay="5000" |
data-hystore-countdown="2026-12-24T23:59:00Z" |
preenche [data-hystore-days], [data-hystore-hours], [data-hystore-minutes], [data-hystore-seconds] (ou o próprio texto) |
data-hystore-animate |
ganha data-visible quando aparece na tela (anime no CSS) |
Elementos que a loja preenche: <hystore-cart-count>, <hystore-account>
(entrar ou o menu da conta; com data-cart mostra também o botão do
carrinho da loja), <hystore-server-status>,
<hystore-online-players>, <hystore-search data-placeholder="...">,
<hystore-skin data-nick="Steve" data-size="128">,
<hystore-video data-youtube="<id ou link>">,
<hystore-app data-app="<nome>"> (um app isolado, veja Apps).
Links dentro da loja (<a href="/loja">) trocam de página sem recarregar;
páginas de conta e pagamento sempre abrem como página nova.
Imagens e fontes
Ponha em assets/ (a ferramenta envia; o servidor confere os 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
Um app é uma pequena página com JavaScript (calculadora, roleta de prêmios,
mini jogo, animação) que o tema coloca com
<hystore-app data-app="roleta"></hystore-app>. Ele fica em apps/<app>/ e
começa pelo index.html:
apps/roleta/index.html a página (obrigatória)
apps/roleta/app.js o script
apps/roleta/app.css o estilo
apps/roleta/app.json { "name": "Roleta de prêmios", "height": 420, "permissions": ["customer"] }
Ele roda isolado, num quadro em outro domínio (hystoreapps.cloud): sem rede
(fetch, XHR e WebSocket bloqueados), sem cookies nem armazenamento, sem
formulários, sem janelas novas, e não consegue mudar nem navegar a página da
loja. Ele fala com a loja só pela ponte:
<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(); // categorias ativas
hystore.addToCart(productId, 1); // 'added', ou 'login' (a loja pede para entrar)
hystore.navigate('/loja'); // uma página da loja
hystore.open('cart'); // 'cart' | 'login' | 'account' | 'search'
hystore.context(); // { locale, colorMode, store: { name, currency } }
hystore.customer(); // { loggedIn, nick, skinUrl }, só com "customer" no app.json
hystore.resize(600); // ou hystore.autosize() para seguir a página
- Tudo é dado público: o preço vem da loja, e o carrinho recebe só o id do produto. Páginas de dinheiro e de conta nunca mostram apps.
- Carregue só os arquivos do próprio app e o
/sdk/v1.js; scripts e estilos de outros sites, quadros e formulários são recusados ao salvar. Imagens podem ser imagensdata:ou as imagens e fontes do tema pelo endereço completo (https://theme-assets.hystore.com.br/..., comoGET /v1/theme/assetse a ferramentalist_assetsdo MCP informam). app.jsoné opcional:heightde 40 a 2000 pixels (320 se faltar),namepara leitores de tela,permissionssócustomer.- Limites: 10 apps por tema, 20 arquivos e 512 KB por app.
- Mensagens demais são ignoradas; pôr no carrinho e trocar de página têm limites próprios, pequenos.
Antes de publicar
npx @hystudios/hystore theme checksem erros.- Todo texto que o visitante lê passa por
| t, em pt, en e es. - O tema funciona nos modos de cor que declara e no celular.
- Os links de carrinho, conta e login apontam para
/carrinho,/perfile/entrar(ou usamdata-hystore-open).
