AI guide
Design your garage with AI
Copy this guide into any AI chat (ChatGPT, Claude, Gemini…) and tell it what you race or build and the look you like. It gives you back your whole garage as JSON (columns, widths, look and every widget in its place) to import in one go. The site checks all of it, so what it gives you is safe to use.
- Copy the guide.
- Paste it into an AI chat, with your wishes.
- On your garage, open the pencil: in Start, paste what it gives you into Import a garage, look it over, and Save.
- To change it later, use Copy my garage as JSON there, and give it to the AI with what to change.
# Design my RC Tuners garage
You are helping me design my **garage** on RC Tuners (rctuners.com), a site for people who race and build radio-controlled cars. A garage is my public profile page. Ask me what I race or build, what I want visitors to see, and the mood I like; then design it.
**What to give me back:** my whole garage as **one JSON value** in a code block, with no comments, in the shape below. I paste it into "Import a garage" (Tune your garage → Start), see it on my page, and save it. If I give you my current garage as JSON, change it and give it back whole, keeping the ids of the widgets you keep.
Keep everything within the rules below: the site checks the garage and refuses anything else, with a note on where it breaks.
**Pages:** I can also have pages of my own beside my garage (like /@me/builds; how many depends on my account, 2 for a member). A page is written exactly like a garage, in the same JSON, except that it has no `comments` widget. Links between them are `/@myhandle` and `/@myhandle/page-address`.
## The garage (one JSON)
```
{ "theme": { ... }, "classes": { ... }, "grid": { "columns": 3, "ratio": "1:2:1" }, "blocks": [ widget, widget, ... ] }
```
**theme** (use the keys, not the names):
- `background`: `sand` (Sand), `clay` (Clay), `moss` (Moss), `lake` (Lake), `asphalt` (Asphalt), `night` (Night track), `sunset-dirt` (Sunset dirt), `grass-field` (Grass field), `water-crossing` (Water crossing), `pit-lane` (Pit lane).
- `palette`: `trackday` (Track day: accent #2f7d3b, text #2a2118, cards #fbf9f4); `clay` (Clay: accent #8a5a2e, text #2a2118, cards #fbf9f4); `water` (Water: accent #1f6f9f, text #16222b, cards #f7fbfd); `checkered` (Checkered flag: accent #111111, text #111111, cards #ffffff); `racing` (Racing red: accent #b3261e, text #241715, cards #fffaf8); `night` (Night race: accent #e2b93b, text #efe8dc, cards #1f1a14).
- `headingFont`: `saira` (Saira Condensed (race number)), `plex` (IBM Plex Sans (clean)), `mono` (IBM Plex Mono (setup sheet)). `bodyFont`: `plex` (IBM Plex Sans), `mono` (IBM Plex Mono).
- `owner` (my name at the top): `show` (Show my name and handle at the top), `hide` (Hide them (a blank canvas)).
- `chrome` (the site's bar and footer around my garage or page): `site` (The site's (light or dark, as each visitor picks)), `garage` (In my garage's colours); default `site`.
- `width`: `contained` (Contained (a card in the page)), `full` (Full width (edge to edge)).
- `padding` (the space around my widgets) and `gap` (the space between them): `normal` (Normal), `tight` (Tight (8px)), `none` (None (edge to edge)). "none" for both puts my widgets edge to edge; my layouts' own `padding` and `margin` then decide the spacing.
**My own look** (instead of the site's sets above):
- `"palette": "custom"` with `"colors": { "accent": "#7a3514", "text": "#1a1410", "card": "#fbf9f4" }`: hex colours as `#rrggbb`. The text must be readable on the card (contrast at least 4.5:1) and the accent at least 3:1 on the card; the site refuses less and says by how much.
- `"background": "custom"` with `"backdrop": { "colors": ["#f6d9a8", "#3a2419"], "angle": 160 }`: 1 colour, or a gradient of up to 4; `angle` 0 to 360 degrees (180 is top to bottom). Hex colours only: no images.
**classes** (optional): styles my layouts share, so a style isn't written again on every element: `"classes": { "my-stripe": { "backgroundColor": "#111111", "padding": "6px 0" } }`, then `"className": "my-stripe"` in any layout (with the site's `g-` classes too, like `"my-stripe g-muted"`). A name is `my-` and lower-case letters, digits and -; each class takes the same style properties and values as a layout's `style` (below); an element's own `style` wins over its classes. At most 50 classes. Use classes whenever the same style repeats.
**grid:** `columns` 1 to 6, and `ratio` their widths, one of: 1: `1`; 2: `1:1`, `2:1`, `1:2`, `3:1`, `1:3`; 3: `1:1:1`, `1:2:1`, `2:1:1`, `1:1:2`; 4: `1:1:1:1`, `2:1:1:2`, `1:2:2:1`; 5: `1:1:1:1:1`; 6: `1:1:1:1:1:1`.
**blocks** (widgets), in order. Each has an `id` (lower-case letters, digits and -, like `text-2`, each its own), a `type`, and its place: `column` (0 is full width across the top; 1 to the number of columns) and `span` (how many columns it covers from there, default 1). Widgets fill the grid in order, row by row, a later one filling a gap above; narrow screens stack them.
- `hero` (Welcome hero, at most 1): `title` (up to 80 characters), `subtitle` (up to 160).
- `text` (at most 12): `title`, `body` (up to 2000; a blank line starts a paragraph).
- `history` (History, at most 4): `title` only. It shows my history on the site; I don't write it.
- `timeline` (Vertical timeline, at most 2): entries I write, top to bottom by date. `title`; `order`: `newest` or `oldest`; `entries`: up to 40 of `{ "date": "2026" | "2026-05" | "2026-05-17", "title": "…" (up to 80), "text": "…" (optional, up to 500), "photo": { "from": "upload" | "setup", "id": n } (optional, one of my photos) }`. Write entries only from what I tell you; keep the ones a garage already has.
- `status` (Status feed, at most 2): `title`, and `count`, how many of my latest statuses it shows (1 to 20, default 5). I post them on the home page; I don't write them here.
- `comments` (Comments, at most 1): members comment on my garage (guests are asked to join). `title`; `display`: `list` (a list, newest first), `carousel` (a carousel, one at a time), `random` (one at random), `selected` (only the ones i select); `count` 1 to 20 (how many show, or take turns in the carousel; default 5); `approval`: `required` (approve each one first), `none` (show them at once), default `required`.
- `models` (Models, at most 2): my models from the site's catalogue (a brand's products, a store's catalogue, or the ones a member runs; I add them on the site, not here). `title` (empty: the site's heading), `count` 1 to 24, `specs` true or false.
- `setup` (Model setup, at most 4): one of my RCs and its setup, as its page on the site has it: its photo, its parts (each with its photo, price and rating), the parts' total, and a link to the whole setup. `title` (empty: the model's name, then "setup"); `rc`, the address of one of my RCs (empty: my first; I find it in Account → Models); `show`: true or false for each of `photo`, `parts`, `prices`, `ratings`. I add parts and photos on the setup's page, not here.
- `visits` (Visitor count, at most 1): how many people visited (unique a day, counted by the site). `title`; `show`: `both` (all time and this week), `total` (all time), `week` (this week).
- `likes` (Like button, at most 1): members like my garage (or page), once each, with the count. `title` (optional).
- `products` (Product list, at most 2; for a store or a brand): my products (Account → Products), as cards with their pictures and prices. `title`; `count` 1 to 24; `order`: `newest` (newest first), `price-low` (price, low to high), `price-high` (price, high to low); `words` (only those with these words, up to 40 characters, or ""); `inStock` (true: only what's in stock).
- `follow` (Follow button, at most 1): members follow me (my statuses in their Following feed), with how many do. `title` (optional).
- `followers` (Followers, at most 2): the latest who follow me, each to their garage, and how many in all. `title`; `count`: 1 to 48; `show`: `all` (faces and names) or `faces` (faces only).
- `badges` (Badges, at most 1): the badges I hold (from events, challenges and the site's achievements), live, each opening its page. `title` (optional); `show`: `all` (pictures and words) or `pictures` (pictures only).
- `gallery` (Gallery, at most 4): my photos, as `grid` (a grid) or `slideshow` (a slideshow) (`display`). `title`; `items`: up to 24 of `{ "from": "upload" | "setup", "id": n, "caption": "…" }` (the caption optional, up to 120 characters). Photos are the member's to upload (in the tune panel): keep the items a garage already has, or leave `items` empty for them to fill.
- `featured` (Featured products, at most 2; for a store or a brand): products I picked, in my order. `title`; `items`: up to 12 of my products' ids (GET /ai/v1/products lists them).
- `tabs` (Tabs, at most 2): tabs, each holding a set of my other widgets (drawn inside it, not in the grid). `title` (optional); `position`: `top` (top), `left` (left side), `right` (right side) (a narrow screen puts them on top); `tabs`: 1 to 6 of `{ "label": up to 24 characters, "blocks": [widget ids, in order] }`. A tab can't hold a menu or another tabs widget, and each widget sits in one tab at most. Give the widgets it holds their own entries in `blocks` as usual (their column and span don't matter).
**Every widget** (any type) may also have:
- `look`: `card` (card), `plain` (plain (no card)), `outlined` (outlined); leave it out for the site's own.
- `className`: my classes (`my-…`, defined in `classes`) and the site's (`g-…`), like `"my-module"`: a my- class's style goes on the widget, so the site's widgets (Status feed, Comments, Timeline…) can match my layouts.
- `profile` (at most 1): `show`, true or false for each of `avatar`, `name`, `handle`, `joined`, `bio`.
- `menu` (at most 1): `items`, 1 to 8 of `{ "label", "kind", "to" }` (label up to 24 characters): kind `section` with `to` a widget's id; `site` with a page like `/@handle`; or `link` with an `https://` address.
- `layout` (Layout (Juris JSON), at most 8): `layout`, a Juris layout (below) as JSON.
At most 24 widgets and 512 KB in all. Columns inside a layout (`g-row`, `g-grid`, `gridTemplateColumns`) are for its own parts; the page's columns are the grid's.
## The Juris layout format
A layout is plain data: a **part** is a text, a list of parts, or an object with exactly one tag:
- `{ "p": "Some words" }`: a tag with its text.
- `{ "ul": [ { "li": "one" }, { "li": "two" } ] }`: a tag with its children.
- `{ "section": { "className": "g-card", "style": { "padding": "24px" }, "children": [ ... ] } }`: a tag with its properties. `"text"` gives it words; `"children"` gives it parts.
**Tags:** div, section, article, aside, header, footer, nav, h1, h2, h3, h4, h5, h6, p, span, strong, em, b, i, small, mark, s, u, sub, sup, br, hr, blockquote, q, cite, abbr, code, pre, kbd, time, ul, ol, li, dl, dt, dd, figure, figcaption, table, caption, thead, tbody, tfoot, tr, th, td, a, details, summary, button, img. Nothing else: no script, style, form, input, video or iframe.
**Pictures (img):** only from the member's own approved domains (Account → Domains on the site; the member applies, proves the domain with a file, a moderator approves it) and their subdomains: `{ "img": { "src": "https://their-domain.com/car.jpg", "alt": "My buggy", "width": 640, "height": 480 } }`. https only; attributes src, alt, width, height, className, style. A picture from any other domain is refused when saved (the answer names it); a member with no approved domain can't use img at all. Ask them which domain, don't guess.
**Drawings (safe SVG):** an `svg` holding only g, path, circle, ellipse, rect, line, polyline, polygon, text, tspan, title, desc, defs, linearGradient, radialGradient, stop, clipPath (and page tags never inside it, nor these outside one). Attributes: viewBox, preserveAspectRatio, xmlns, width, height, x, y, dx, dy, cx, cy, r, rx, ry, fx, fy, x1, y1, x2, y2, points, d, fill, stroke, stroke-width, stroke-linecap, stroke-linejoin, stroke-dasharray, stroke-miterlimit, opacity, fill-opacity, stroke-opacity, fill-rule, clip-rule, clip-path, transform, font-size, font-weight, text-anchor, dominant-baseline, letter-spacing, gradientUnits, gradientTransform, offset, stop-color, stop-opacity, clipPathUnits, plus id, className, style, title, role, aria-label; numbers and colours only (hex, none, currentColor, `var(--g-accent)`, `var(--g-text)`, `var(--g-card)`), and `url(#id)` only to an id in the same layout (a gradient in `defs`). Text goes in `text`, `tspan`, `title` or `desc` as `"text"`. No use, image, links, foreignObject, style, filters, masks or animation. Example: `{ "svg": { "viewBox": "0 0 100 100", "width": 96, "role": "img", "aria-label": "Car 42", "children": [ { "circle": { "cx": 50, "cy": 50, "r": 40, "fill": "var(--g-accent)" } }, { "text": { "x": 50, "y": 58, "text-anchor": "middle", "font-size": 28, "fill": "#ffffff", "text": "42" } } ] } }`.
**Properties:** `text`, `children`, `className`, `id`, `style`, and `title`, `lang`, `dir` (ltr, rtl, auto), `hidden`, `role` (note, group, list, listitem, region, img, separator, presentation), `aria-label`, `aria-hidden` (true, false), `href` (on a), `dateTime` (on time), `cite` (on blockquote, q), `open` (on details), `colSpan` (on td, th), `rowSpan` (on td, th), `scope` (on th), `start` (on ol), `reversed` (on ol), `type` (on button), `src` (on img), `alt` (on img), `width` (on img), `height` (on img), `data-rct`, `data-year`, `data-section`. Never `on…` handlers, never HTML.
**Classes:** my garage's own, starting with `my-` (defined in `classes`), and the site's, starting with `g-`:
- `g-card`: a card: the palette's card colour, rounded, padded (the look of the site's own widgets)
- `g-row`: children side by side, wrapping, with a gap
- `g-grid`: children in an even grid that fits the width (each at least 180px)
- `g-center`: centred text
- `g-muted`: quieter text
- `g-accent`: text in the palette's accent colour
- `g-big`: larger text
- `g-pill`: a small rounded label in the accent colour
- `g-avatar-sm`: with data-rct-avatar: a smaller avatar (48px instead of 96px)
**Styles:** an object of these properties only:
- `color`: a colour (see Colours)
- `backgroundColor`: a colour (see Colours)
- `borderColor`: a colour (see Colours)
- `textAlign`: left, right, center, justify, start, end
- `fontWeight`: 400, 500, 600, 700, 800, normal, bold
- `fontStyle`: normal, italic
- `textTransform`: none, uppercase, lowercase, capitalize
- `fontSize`: 0.5rem to 3.99rem, like 1.25rem
- `letterSpacing`: a small em value, like 0.05em
- `lineHeight`: 1 to 2, like 1.4
- `margin`: 1 to 4 lengths or auto, like "0 auto" or "12px 0"
- `marginTop`: a length
- `marginBottom`: a length
- `padding`: 1 to 4 lengths, like "16px 24px"
- `paddingTop`: a length
- `paddingBottom`: a length
- `gap`: a length
- `borderRadius`: a length
- `borderWidth`: 0 to 8px
- `borderStyle`: none, solid, dashed, dotted
- `display`: block, inline, inline-block, flex, grid, none
- `flexWrap`: wrap, nowrap
- `flexDirection`: row, column
- `justifyContent`: start, center, end, space-between, space-around, flex-start, flex-end
- `alignItems`: start, center, end, stretch, baseline, flex-start, flex-end
- `gridTemplateColumns`: repeat(1 to 6, 1fr), or repeat(n, minmax(120px, 1fr))
- `maxWidth`: a length
- `width`: a percentage, px or rem (like 50%, 240px), or auto
- `opacity`: 0 to 1, like 0.8
- `backgroundImage`: a gradient of 2 to 6 colours (see Colours), with an angle and stops: "linear-gradient(160deg, var(--g-accent), #111111 80%)" or stripes "repeating-linear-gradient(135deg, #3a4148 0 12px, #e8a33d 12px 24px)"; never an image
- `boxShadow`: one shadow, "0 2px 8px #00000040" (offsets and blur to 99px, spread to 9px, a colour), "inset …", or none
- `borderTop`: one side's border, "2px solid var(--g-accent)" (0 to 8px, solid/dashed/dotted, a colour), or none
- `borderRight`: like borderTop
- `borderBottom`: like borderTop
- `borderLeft`: like borderTop
- `marginLeft`: a length
- `marginRight`: a length
- `paddingLeft`: a length
- `paddingRight`: a length
- `rowGap`: a length
- `columnGap`: a length
- `height`: px or rem to 999, or auto
- `minHeight`: like height
- `maxHeight`: like height
- `minWidth`: 0, px, rem or %
- `gridColumn`: "span 2" (1 to 6), "1 / span 2", "2 / -1" or "1 / -1" (a whole row)
- `gridRow`: "span 2" (1 to 6)
- `alignSelf`: auto, start, center, end, stretch, baseline
- `justifySelf`: auto, start, center, end, stretch
- `flex`: none, auto, or "1", "1 1", "1 1 200px"
- `flexGrow`: 0 to 9
- `flexShrink`: 0 to 9
- `flexBasis`: auto, 0, px, rem or %
- `order`: -9 to 9
- `aspectRatio`: "16 / 9", "1 / 1", or auto
- `fontFamily`: the site's fonts: var(--g-heading), var(--g-body) or var(--g-mono)
- `fontVariantNumeric`: normal, or tabular-nums (numbers line up: setup sheets, lap times)
- `textDecoration`: none, underline, line-through
- `whiteSpace`: normal, nowrap, pre-line
- `overflowWrap`: normal, anywhere, break-word
- `verticalAlign`: top, middle, bottom, baseline
- `listStyle`: none, disc, decimal, square
- `borderCollapse`: collapse, separate
**Never in styles** (the site refuses them): position, z-index, transform, animation, url(), images, or any font but the site's. Everything a garage draws stays inside it.
**Colours** (in styles): a hex colour like `#1f6f9f`, `transparent`, `currentColor`, or my palette's own: `var(--g-accent)`, `var(--g-text)`, `var(--g-card)`. No `url()`, no images.
**Links** (`href` on `a`): an `https://` address (it opens in a new tab, with a warning first), a page on the site (`/@handle`), or a part of my garage (`#id`, matching an `id` in a layout). Nothing else.
**My details, filled in by the site:** put `true` on an element to have the site put in `data-rct-name` (my name), `data-rct-handle` (my @handle), `data-rct-joined` ("Joined September 2026"), `data-rct-bio` (my bio), `data-rct-avatar` (my avatar). Whatever the element held is replaced.
**The site's behaviours:** `"data-rct"` set to `menu`, `history`, `outlink`. Links to `#id` scroll there; outside links warn before leaving. Layouts can't have any other behaviour or code.
**Limits:** elements at most 12 deep inside each other (a list of children isn't a level), 400 parts, 2000 characters in one text, 12000 characters of text in all, and 48 KB of JSON per layout, measured minified (indent it as you like: spaces and line breaks don't count).
## A whole garage that passes
```json
{
"theme": {
"background": "asphalt",
"palette": "racing",
"headingFont": "saira",
"bodyFont": "plex",
"owner": "hide",
"width": "full"
},
"grid": {
"columns": 3,
"ratio": "1:2:1"
},
"blocks": [
{
"id": "hero-1",
"type": "hero",
"column": 0,
"title": "Fast on astro, faster in the pits",
"subtitle": "1/10 2WD and 4WD buggy, club racing."
},
{
"id": "menu-1",
"type": "menu",
"column": 0,
"items": [
{
"label": "Setup",
"kind": "section",
"to": "layout-1"
},
{
"label": "Results",
"kind": "section",
"to": "history-1"
}
]
},
{
"id": "profile-1",
"type": "profile",
"column": 1,
"show": {
"avatar": true,
"name": true,
"handle": true,
"joined": true,
"bio": true
}
},
{
"id": "layout-1",
"type": "layout",
"column": 2,
"layout": {
"section": {
"className": "g-card",
"children": [
{
"h3": "Setup sheet: 1/10 2WD buggy, astro"
},
{
"table": [
{
"thead": [
{
"tr": [
{
"th": "Setting"
},
{
"th": "Front"
},
{
"th": "Rear"
}
]
}
]
},
{
"tbody": [
{
"tr": [
{
"th": {
"scope": "row",
"text": "Ride height"
}
},
{
"td": "20 mm"
},
{
"td": "22 mm"
}
]
},
{
"tr": [
{
"th": {
"scope": "row",
"text": "Shock oil"
}
},
{
"td": "30 wt"
},
{
"td": "27.5 wt"
}
]
},
{
"tr": [
{
"th": {
"scope": "row",
"text": "Pinion / spur"
}
},
{
"td": {
"colSpan": 2,
"text": "25T / 78T"
}
}
]
}
]
}
]
}
]
}
}
},
{
"id": "history-1",
"type": "history",
"column": 3,
"title": "Results"
},
{
"id": "status-1",
"type": "status",
"column": 3,
"title": "Latest from the pits",
"count": 3
},
{
"id": "models-1",
"type": "models",
"column": 2,
"title": "What I run",
"count": 6,
"specs": true
},
{
"id": "visits-1",
"type": "visits",
"column": 2,
"title": "Visitors",
"show": "both"
},
{
"id": "likes-1",
"type": "likes",
"column": 2,
"title": ""
},
{
"id": "products-1",
"type": "products",
"column": 2,
"title": "In the shop",
"count": 8,
"order": "newest",
"words": "",
"inStock": true
},
{
"id": "featured-1",
"type": "featured",
"column": 2,
"title": "This week's picks",
"items": []
},
{
"id": "gallery-1",
"type": "gallery",
"column": 3,
"title": "Track days",
"display": "grid",
"items": []
},
{
"id": "badges-1",
"type": "badges",
"column": 3,
"title": "Badges",
"show": "all"
},
{
"id": "timeline-1",
"type": "timeline",
"column": 3,
"title": "My season",
"order": "newest",
"entries": [
{
"date": "2026-05-17",
"title": "First A-main",
"text": "Third on the grid, second at the flag."
},
{
"date": "2026-03",
"title": "Built the X4"
}
]
},
{
"id": "follow-1",
"type": "follow",
"column": 2,
"title": ""
},
{
"id": "followers-1",
"type": "followers",
"column": 3,
"title": "Followers",
"count": 12,
"show": "all"
},
{
"id": "setup-1",
"type": "setup",
"column": 2,
"title": "",
"rc": "",
"show": {
"photo": true,
"parts": true,
"prices": true,
"ratings": true
}
},
{
"id": "tabs-1",
"type": "tabs",
"column": 3,
"title": "",
"position": "top",
"tabs": [
{
"label": "Latest",
"blocks": [
"status-1"
]
},
{
"label": "Results",
"blocks": [
"history-1"
]
}
]
},
{
"id": "comments-1",
"type": "comments",
"column": 2,
"title": "From the pits",
"display": "carousel",
"approval": "required",
"count": 5
},
{
"id": "text-1",
"type": "text",
"column": 1,
"span": 3,
"title": "What I'm chasing",
"body": "A clean A-main at the regional.\n\nThen a lighter rear end for the high-grip days."
}
]
}
```
## My own look and classes, in a garage that passes
```json
{
"theme": {
"palette": "custom",
"colors": {
"accent": "#7a3514",
"text": "#1a1410",
"card": "#fbf9f4"
},
"background": "custom",
"backdrop": {
"colors": [
"#f6d9a8",
"#c98f58",
"#3a2419"
],
"angle": 160
},
"headingFont": "saira",
"bodyFont": "mono"
},
"classes": {
"my-panel": {
"borderStyle": "dashed",
"borderWidth": "2px",
"borderColor": "var(--g-accent)",
"borderRadius": "6px",
"padding": "12px 14px"
},
"my-check-dark": {
"display": "block",
"width": "5%",
"padding": "6px 0",
"backgroundColor": "#111111"
},
"my-check-light": {
"display": "block",
"width": "5%",
"padding": "6px 0",
"backgroundColor": "#fbf9f4"
}
},
"grid": {
"columns": 1,
"ratio": "1"
},
"blocks": [
{
"id": "layout-1",
"type": "layout",
"column": 0,
"layout": {
"section": {
"className": "g-card",
"children": [
{
"div": {
"style": {
"display": "flex"
},
"aria-hidden": "true",
"children": [
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
},
{
"span": {
"className": "my-check-dark"
}
},
{
"span": {
"className": "my-check-light"
}
}
]
}
},
{
"div": {
"className": "my-panel",
"style": {
"marginTop": "12px"
},
"children": [
{
"h4": {
"className": "g-accent",
"text": "Front"
}
},
{
"p": "Ride height 20 mm, 30 wt oil."
}
]
}
}
]
}
}
}
]
}
```
## Layouts that pass
### A header with the owner's details (for a garage whose name at the top is hidden)
```json
{
"section": {
"className": "g-card g-row",
"style": {
"alignItems": "center",
"gap": "20px"
},
"children": [
{
"span": {
"data-rct-avatar": true
}
},
{
"div": [
{
"h1": {
"data-rct-name": true
}
},
{
"p": {
"className": "g-muted",
"data-rct-handle": true
}
},
{
"p": {
"data-rct-bio": true
}
}
]
}
]
}
}
```
### A setup sheet
```json
{
"section": {
"className": "g-card",
"children": [
{
"h3": "Setup sheet: 1/10 2WD buggy, astro"
},
{
"table": [
{
"thead": [
{
"tr": [
{
"th": "Setting"
},
{
"th": "Front"
},
{
"th": "Rear"
}
]
}
]
},
{
"tbody": [
{
"tr": [
{
"th": {
"scope": "row",
"text": "Ride height"
}
},
{
"td": "20 mm"
},
{
"td": "22 mm"
}
]
},
{
"tr": [
{
"th": {
"scope": "row",
"text": "Shock oil"
}
},
{
"td": "30 wt"
},
{
"td": "27.5 wt"
}
]
},
{
"tr": [
{
"th": {
"scope": "row",
"text": "Pinion / spur"
}
},
{
"td": {
"colSpan": 2,
"text": "25T / 78T"
}
}
]
}
]
}
]
}
]
}
}
```
### A band in the accent colour, with links out
```json
{
"section": {
"className": "g-card g-center",
"style": {
"backgroundColor": "var(--g-accent)",
"color": "var(--g-card)",
"padding": "32px 24px"
},
"children": [
{
"h2": "Watch me race"
},
{
"p": "Club rounds every second Sunday."
},
{
"div": {
"className": "g-row",
"style": {
"justifyContent": "center"
},
"children": [
{
"a": {
"href": "https://www.youtube.com/",
"text": "YouTube",
"className": "g-pill"
}
},
{
"a": {
"href": "#story",
"text": "My story",
"className": "g-pill"
}
}
]
}
},
{
"p": {
"id": "story",
"className": "g-muted",
"text": "Started on a hand-me-down buggy in 2019; racing club rounds since."
}
}
]
}
}
```
## Designing well
- Say it the RC way: setups, gearing, tyres, tracks, results, builds.
- Use `g-card` for panels and my palette's colours (`var(--g-accent)` and friends), so the layout follows my theme if I change it.
- Keep text readable: good contrast, a few short sections rather than one long one.
- Use a column and span plan: for example 3 columns at 1:2:1, a full-width welcome, a wide story in the middle.
- **For tabs, use the `tabs` widget**, not tags inside a layout: split the content into several Layout widgets (one per tab, like "Parts", "Service log", "Timing"), and give a `tabs` widget their ids. Real tab switching (click, arrow keys) is the site's; `details`/`summary` inside a layout is an accordion, not tabs.