{
  "schemaVersion": 1,
  "version": "0.1.1",
  "cdn": {
    "css": "https://cdn.jsdelivr.net/npm/retrostrap@0.1.1/dist/retrostrap.min.css",
    "js": "https://cdn.jsdelivr.net/npm/retrostrap@0.1.1/dist/retrostrap.min.js",
    "patterns": "https://cdn.jsdelivr.net/npm/retrostrap@0.1.1/dist/retrostrap-patterns.css",
    "theme": "https://cdn.jsdelivr.net/npm/retrostrap@0.1.1/dist/themes/{name}.css"
  },
  "laws": {
    "version": 1,
    "palette": {
      "law": "Only the 216 web-safe colors plus the 16 classic named colors. UI colors are opaque.",
      "colors": [
        "#000000",
        "#000033",
        "#000066",
        "#000099",
        "#0000CC",
        "#0000FF",
        "#003300",
        "#003333",
        "#003366",
        "#003399",
        "#0033CC",
        "#0033FF",
        "#006600",
        "#006633",
        "#006666",
        "#006699",
        "#0066CC",
        "#0066FF",
        "#009900",
        "#009933",
        "#009966",
        "#009999",
        "#0099CC",
        "#0099FF",
        "#00CC00",
        "#00CC33",
        "#00CC66",
        "#00CC99",
        "#00CCCC",
        "#00CCFF",
        "#00FF00",
        "#00FF33",
        "#00FF66",
        "#00FF99",
        "#00FFCC",
        "#00FFFF",
        "#330000",
        "#330033",
        "#330066",
        "#330099",
        "#3300CC",
        "#3300FF",
        "#333300",
        "#333333",
        "#333366",
        "#333399",
        "#3333CC",
        "#3333FF",
        "#336600",
        "#336633",
        "#336666",
        "#336699",
        "#3366CC",
        "#3366FF",
        "#339900",
        "#339933",
        "#339966",
        "#339999",
        "#3399CC",
        "#3399FF",
        "#33CC00",
        "#33CC33",
        "#33CC66",
        "#33CC99",
        "#33CCCC",
        "#33CCFF",
        "#33FF00",
        "#33FF33",
        "#33FF66",
        "#33FF99",
        "#33FFCC",
        "#33FFFF",
        "#660000",
        "#660033",
        "#660066",
        "#660099",
        "#6600CC",
        "#6600FF",
        "#663300",
        "#663333",
        "#663366",
        "#663399",
        "#6633CC",
        "#6633FF",
        "#666600",
        "#666633",
        "#666666",
        "#666699",
        "#6666CC",
        "#6666FF",
        "#669900",
        "#669933",
        "#669966",
        "#669999",
        "#6699CC",
        "#6699FF",
        "#66CC00",
        "#66CC33",
        "#66CC66",
        "#66CC99",
        "#66CCCC",
        "#66CCFF",
        "#66FF00",
        "#66FF33",
        "#66FF66",
        "#66FF99",
        "#66FFCC",
        "#66FFFF",
        "#990000",
        "#990033",
        "#990066",
        "#990099",
        "#9900CC",
        "#9900FF",
        "#993300",
        "#993333",
        "#993366",
        "#993399",
        "#9933CC",
        "#9933FF",
        "#996600",
        "#996633",
        "#996666",
        "#996699",
        "#9966CC",
        "#9966FF",
        "#999900",
        "#999933",
        "#999966",
        "#999999",
        "#9999CC",
        "#9999FF",
        "#99CC00",
        "#99CC33",
        "#99CC66",
        "#99CC99",
        "#99CCCC",
        "#99CCFF",
        "#99FF00",
        "#99FF33",
        "#99FF66",
        "#99FF99",
        "#99FFCC",
        "#99FFFF",
        "#CC0000",
        "#CC0033",
        "#CC0066",
        "#CC0099",
        "#CC00CC",
        "#CC00FF",
        "#CC3300",
        "#CC3333",
        "#CC3366",
        "#CC3399",
        "#CC33CC",
        "#CC33FF",
        "#CC6600",
        "#CC6633",
        "#CC6666",
        "#CC6699",
        "#CC66CC",
        "#CC66FF",
        "#CC9900",
        "#CC9933",
        "#CC9966",
        "#CC9999",
        "#CC99CC",
        "#CC99FF",
        "#CCCC00",
        "#CCCC33",
        "#CCCC66",
        "#CCCC99",
        "#CCCCCC",
        "#CCCCFF",
        "#CCFF00",
        "#CCFF33",
        "#CCFF66",
        "#CCFF99",
        "#CCFFCC",
        "#CCFFFF",
        "#FF0000",
        "#FF0033",
        "#FF0066",
        "#FF0099",
        "#FF00CC",
        "#FF00FF",
        "#FF3300",
        "#FF3333",
        "#FF3366",
        "#FF3399",
        "#FF33CC",
        "#FF33FF",
        "#FF6600",
        "#FF6633",
        "#FF6666",
        "#FF6699",
        "#FF66CC",
        "#FF66FF",
        "#FF9900",
        "#FF9933",
        "#FF9966",
        "#FF9999",
        "#FF99CC",
        "#FF99FF",
        "#FFCC00",
        "#FFCC33",
        "#FFCC66",
        "#FFCC99",
        "#FFCCCC",
        "#FFCCFF",
        "#FFFF00",
        "#FFFF33",
        "#FFFF66",
        "#FFFF99",
        "#FFFFCC",
        "#FFFFFF",
        "#C0C0C0",
        "#808080",
        "#800000",
        "#800080",
        "#008000",
        "#808000",
        "#000080",
        "#008080"
      ]
    },
    "fonts": {
      "law": "Nine sanctioned era font stacks; no @font-face in core.",
      "stacks": {
        "serif": "\"Times New Roman\", Times, serif",
        "sans": "Verdana, Geneva, \"DejaVu Sans\", sans-serif",
        "narrow": "Tahoma, \"Segoe UI\", Geneva, sans-serif",
        "arial": "Arial, Helvetica, sans-serif",
        "comic": "\"Comic Sans MS\", \"Comic Neue\", \"Chalkboard SE\", cursive",
        "mono": "\"Courier New\", Courier, monospace",
        "display": "Impact, \"Arial Black\", Haettenschweiler, sans-serif",
        "fancy": "Georgia, \"Times New Roman\", serif",
        "trebuchet": "\"Trebuchet MS\", Tahoma, sans-serif"
      },
      "sizesPx": [
        10,
        13,
        16,
        18,
        24,
        32,
        48
      ]
    },
    "shape": {
      "law": "border-radius is 0 everywhere; shadows have 0 blur. Rounding only via border-image pixel assets.",
      "borderRadius": 0,
      "shadowBlur": 0
    },
    "motion": {
      "law": "Only linear and steps() easing. Everything honors prefers-reduced-motion.",
      "easing": [
        "linear",
        "steps",
        "step-start",
        "step-end"
      ],
      "blinkHz": 1,
      "marqueeSpeedsPxPerSec": [
        30,
        60,
        120
      ],
      "maxFlashesPerSecond": 3
    },
    "decency": {
      "law": "No autoplaying audio, no tracking, no external network calls from shipped code, progressive enhancement.",
      "autoplayAudio": false,
      "tracking": false,
      "externalRequests": false
    },
    "breakpointsPx": {
      "vga": 640,
      "svga": 800,
      "xga": 1024,
      "sxga": 1280
    }
  },
  "prompt": "You are building a web page with retrostrap v0.1.1, a framework that\nmakes pages look like the 1996-2003 web while staying responsive and\naccessible.\n\nHard rules, violating any of these breaks the page's era-fidelity:\n1. Use only documented rs- classes for UI. Do not write custom CSS unless\n   asked; if you must, obey: colors from the 216 web-safe + 16 named set\n   only; border-radius 0; shadows with 0 blur; easing only linear or\n   steps(); fonts only via --rs-font-* tokens.\n2. Structure: <body> gets the theme tile; wrap content in\n   <div class=\"rs-page rs-container\">. Pick ONE layout recipe\n   (rs-layout--sidebar-left, --holy-grail, rs-frames…). Real landmarks:\n   header, nav, main, footer. Headings h1-h6 in order.\n3. Pick ONE theme via data-rs-theme on <html>. Do not mix theme files.\n4. Widgets (the Toybox): at most TWO decorative widgets per page,\n   declared via data-rs-widgets=\"…\" on any element, with their\n   data-rs-<widget>-<option> attributes. Never add autoplaying audio.\n   Never add tracking.\n5. Keep text sizes on the rs-font-1..7 scale. Links stay underlined. Use\n   rs-pagination, never infinite scroll. Use tables for data with rs-table.\n6. Copy tone: enthusiastic 1999 webmaster, no modern marketing speak, no\n   emoji (use the smilies widget). Tildes and ASCII dividers welcome.\n7. Accessibility is non-negotiable: alt text everywhere, label every\n   input, don't remove focus outlines, respect the component snippets' ARIA.\n\nProcess: 1) choose theme + layout recipe; 2) compose from cheatsheet\nsnippets; 3) add up to two widgets; 4) self-check against the rules above\n(they mirror Retrostrap.audit()).",
  "components": [
    {
      "id": "alert",
      "kind": "component",
      "label": "Alert",
      "summary": "An inline message bar in info, warn and error flavors; no animation, it is just there.",
      "era": "The plain message bar, \"Your entry has been added\", sitting in the page like news, no animation.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-alert",
          "role": "inline message bar: icon slot plus text"
        },
        {
          "name": "rs-alert--info",
          "role": "modifier: silver"
        },
        {
          "name": "rs-alert--warn",
          "role": "modifier: #FFFFCC"
        },
        {
          "name": "rs-alert--error",
          "role": "modifier: #FFCCCC with red border; carries role=\"alert\""
        }
      ],
      "a11y": "Static variants are just text where they stand; only --error carries role=\"alert\", so errors injected after load get announced.",
      "assets": [
        "alert-info",
        "alert-warn",
        "alert-error"
      ],
      "tags": [
        "feedback",
        "message"
      ],
      "snippet": "<p class=\"rs-alert rs-alert--info\">Your entry has been added to the guestbook.</p>\n<p class=\"rs-alert rs-alert--warn\">The jukebox is down for maintenance until Sunday.</p>\n<p class=\"rs-alert rs-alert--error\" role=\"alert\">Upload failed: the file is bigger than a floppy.</p>"
    },
    {
      "id": "ascii",
      "kind": "component",
      "label": "ASCII art",
      "summary": "A <pre> block for ASCII art and text banners, marked up as an image so readers announce a label, not every slash.",
      "era": "Signatures, textfile logos, and figlet banners, the art you made when the only tool guaranteed to render was the font.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-ascii",
          "role": "the art block: tight-leading monospace on <pre>, scrolls sideways if wide"
        },
        {
          "name": "rs-ascii--center",
          "role": "centers the block while the art inside stays left-aligned"
        },
        {
          "name": "rs-ascii--framed",
          "role": "a raised bevel frame around the art"
        },
        {
          "name": "rs-ascii--sig",
          "role": "the forum signature variant: smaller and quieter, under a hairline"
        }
      ],
      "a11y": "Mark it up as <pre role=\"img\"> with an aria-label that says what the art shows; assistive tech announces the label instead of reading every character. Wide art scrolls inside its own box, so it never breaks the page on a phone. For text you want read aloud (code, tables), use rs-dos instead.",
      "assets": [],
      "tags": [
        "typography",
        "decoration",
        "mono"
      ],
      "snippet": "<!-- role=\"img\" + aria-label: the reader hears \"A small cat\", not the slashes -->\n<pre class=\"rs-ascii rs-ascii--framed rs-ascii--center\" role=\"img\" aria-label=\"A small cat, in ASCII art\">\n /\\_/\\\n( o.o )\n &gt; ^ &lt;</pre>"
    },
    {
      "id": "avatar",
      "kind": "component",
      "label": "Avatar",
      "summary": "An 80×80 pixelated avatar image with a 1px border; --small drops it to 40×40.",
      "era": "The 80×80 forum avatar, dithered to 256 colors and worn like a uniform on every message board of 2001.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-avatar",
          "role": "80×80, 1px border, image-rendering pixelated"
        },
        {
          "name": "rs-avatar--small",
          "role": "modifier: 40×40"
        }
      ],
      "a11y": "It is an <img>: alt text is required and names who the avatar stands for.",
      "assets": [],
      "tags": [
        "content",
        "image",
        "forum"
      ],
      "snippet": "<img class=\"rs-avatar\" src=\"avatar.gif\" alt=\"Vector~Prime's avatar: a knight in mirrored shades\" width=\"80\" height=\"80\">\n<img class=\"rs-avatar rs-avatar--small\" src=\"avatar.gif\" alt=\"Vector~Prime's avatar, 40 pixels of knight\" width=\"40\" height=\"40\">"
    },
    {
      "id": "award",
      "kind": "component",
      "label": "Award",
      "summary": "A trophy-shelf block: one award image and the caption telling who gave it and when.",
      "era": "\"This site won the Golden Floppy, March 1999\", awards traded between webmasters like merit badges.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-award",
          "role": "trophy-shelf block for one award image"
        },
        {
          "name": "rs-award__caption",
          "role": "the who/what/when line"
        }
      ],
      "a11y": "A <figure> with <figcaption>: the alt names the award, the caption tells the story.",
      "assets": [],
      "tags": [
        "content",
        "award",
        "image"
      ],
      "snippet": "<figure class=\"rs-award\">\n  <img src=\"golden-floppy.gif\" alt=\"Golden Floppy Award trophy\" width=\"120\" height=\"90\">\n  <figcaption class=\"rs-award__caption\">This site won the Golden Floppy, March 1999.</figcaption>\n</figure>"
    },
    {
      "id": "badge",
      "kind": "component",
      "label": "Badge",
      "summary": "Inline flashing tags, NEW!, UPDATED!, HOT!, COOL!, for links you touched this month.",
      "era": "The flashing NEW! gif planted beside every link the webmaster touched this month.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-badge",
          "role": "inline tag chip"
        },
        {
          "name": "rs-badge--new",
          "role": "modifier: NEW! in red on yellow"
        },
        {
          "name": "rs-badge--updated",
          "role": "modifier: UPDATED!"
        },
        {
          "name": "rs-badge--hot",
          "role": "modifier: HOT! with flame icon"
        },
        {
          "name": "rs-badge--cool",
          "role": "modifier: COOL! in blue"
        }
      ],
      "a11y": "The badge is real text, read inline where it sits; add rs-blink for the flash and reduced motion renders it static bold.",
      "assets": [],
      "tags": [
        "content",
        "inline",
        "new"
      ],
      "snippet": "<ul>\n  <li><a href=\"#\">Downloads</a> <sup><span class=\"rs-badge rs-badge--new rs-blink\">NEW!</span></sup></li>\n  <li><a href=\"#\">Cheats &amp; codes</a> <sup><span class=\"rs-badge rs-badge--updated\">UPDATED!</span></sup></li>\n  <li><a href=\"#\">MIDI jukebox</a> <sup><span class=\"rs-badge rs-badge--hot\">HOT!</span></sup></li>\n  <li><a href=\"#\">Winter skin pack</a> <sup><span class=\"rs-badge rs-badge--cool\">COOL!</span></sup></li>\n</ul>"
    },
    {
      "id": "banner",
      "kind": "component",
      "label": "Banner",
      "summary": "A 468×60 banner shell that scales proportionally below 500px, the one sanctioned proportional scale.",
      "era": "The 468×60 full banner of 1996: the only ad size that ever mattered.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-banner",
          "role": "468×60 shell; scales down proportionally below 500px viewports"
        }
      ],
      "a11y": "An image link: the alt text names the destination, not the artwork.",
      "assets": [],
      "tags": [
        "content",
        "banner",
        "468x60"
      ],
      "snippet": "<a class=\"rs-banner\" href=\"#\">\n  <img src=\"banner-468x60.gif\" alt=\"Star Quest 3D fan zone, click to warp in\" width=\"468\" height=\"60\">\n</a>"
    },
    {
      "id": "blink",
      "kind": "component",
      "label": "Blink",
      "summary": "Inline text that blinks at exactly one hertz.",
      "era": "Netscape's <blink> tag, born of a 1994 bar joke about Lynx, back, and holding steady at one flash per second.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-blink",
          "role": "1Hz steps(1) opacity blink; bold + underline under reduced motion"
        }
      ],
      "a11y": "One flash per second sits well under the three-per-second photosensitivity ceiling. Under prefers-reduced-motion it renders as static bold plus underline, the emphasis survives, the blinking does not.",
      "assets": [],
      "tags": [
        "typography",
        "motion"
      ],
      "snippet": "<p><span class=\"rs-blink\">NEW!</span> Three fresh wallpapers in the downloads section!</p>\n<p><span class=\"rs-blink\">HOT!</span> My guestbook just passed 100 entries. Thank you, visitors!</p>"
    },
    {
      "id": "blinkie",
      "kind": "component",
      "label": "Blinkie",
      "summary": "The ~150×20 glitter strip that declared your allegiances in a sidebar or signature.",
      "era": "Blinkies, skinny animated badges with a shiny border and a phrase like \"I love my cat\".",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-blinkie",
          "role": "the 150×20 strip; pair with rs-blink for the shimmer"
        },
        {
          "name": "rs-blinkie--hot",
          "role": "modifier: maroon and amber"
        },
        {
          "name": "rs-blinkie--cool",
          "role": "modifier: black and cyan"
        },
        {
          "name": "rs-blinkie--candy",
          "role": "modifier: fuchsia and white"
        }
      ],
      "a11y": "Just a small styled element; if it links somewhere, it's a real link. Add rs-blink for the classic shimmer (which stops under reduced motion).",
      "assets": [],
      "tags": [
        "content",
        "decoration"
      ],
      "snippet": "<p class=\"rs-badge-wall\">\n  <span class=\"rs-blinkie rs-blink\">I &lt;3 MY CAT</span>\n  <span class=\"rs-blinkie rs-blinkie--hot\">HTML 4 EVER</span>\n  <span class=\"rs-blinkie rs-blinkie--cool\">SURF THE WEB</span>\n  <span class=\"rs-blinkie rs-blinkie--candy\">~*~ KAWAII ~*~</span>\n</p>"
    },
    {
      "id": "breadcrumbs",
      "kind": "component",
      "label": "Breadcrumbs",
      "summary": "The \"You are here:\" trail as an ordered list of links.",
      "era": "\"You are here:\", the directory-portal trail printed above every category page, mall-map style.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-breadcrumbs",
          "role": "the trail; bold prefix and > separators come from the CSS"
        }
      ],
      "a11y": "<nav aria-label=\"You are here\"> around an <ol>; the current page gets aria-current=\"page\". Separators are CSS, invisible to readers.",
      "assets": [],
      "tags": [
        "navigation",
        "trail"
      ],
      "snippet": "<nav class=\"rs-breadcrumbs\" aria-label=\"You are here\">\n  <ol>\n    <li><a href=\"#\">Home</a></li>\n    <li><a href=\"#\">Games</a></li>\n    <li aria-current=\"page\">Star Quest 3D</li>\n  </ol>\n</nav>"
    },
    {
      "id": "btn",
      "kind": "component",
      "label": "Button",
      "summary": "The beveled system button; pressing it nudges the label one pixel down-right.",
      "era": "The silver Windows 95 push button, raised at rest, sunken while you hold it, the most-pressed rectangle of the decade.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-btn",
          "role": "the raised bevel button; :active sinks it and nudges the label 1px"
        },
        {
          "name": "rs-btn--primary",
          "role": "modifier: bold, accent-faced, the default action"
        },
        {
          "name": "rs-btn--link",
          "role": "modifier: dressed as a link, still a button"
        },
        {
          "name": "rs-btn--small",
          "role": "modifier: font step 1"
        },
        {
          "name": "rs-btn--large",
          "role": "modifier: font step 4"
        },
        {
          "name": "rs-btn--icon",
          "role": "modifier: 16×16 icon beside the text"
        }
      ],
      "a11y": "Always a real <button>, focus, Space and Enter come free, and the --link disguise is still a button underneath. Disabled buttons keep the engraved gray text, which WCAG exempts as inactive UI.",
      "assets": [],
      "tags": [
        "forms",
        "button",
        "bevel"
      ],
      "snippet": "<button type=\"button\" class=\"rs-btn\">OK</button>\n<button type=\"button\" class=\"rs-btn rs-btn--primary\">Sign my guestbook!</button>\n<button type=\"button\" class=\"rs-btn rs-btn--small\">maybe later</button>"
    },
    {
      "id": "button88",
      "kind": "component",
      "label": "88×31 button",
      "summary": "The 88×31 button as an element: fixed size forever, built for badge walls.",
      "era": "The 88×31 button, the micro-banner standard every site traded and no committee ever ratified.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-button88",
          "role": "fixed 88×31 box, pixel border, one line of text; never scales"
        },
        {
          "name": "rs-button88--navy",
          "role": "baked navy-on-white palette"
        },
        {
          "name": "rs-button88--maroon",
          "role": "baked maroon-with-gold palette"
        },
        {
          "name": "rs-button88--teal",
          "role": "baked teal-on-white palette"
        }
      ],
      "a11y": "A real link (or image link) whose text or alt names the destination, 88×31 pixels is no excuse for an empty accessible name.",
      "assets": [],
      "tags": [
        "content",
        "badge",
        "88x31"
      ],
      "snippet": "<div class=\"rs-badge-wall\">\n  <a class=\"rs-button88\" href=\"#\">STAR QUEST 3D</a>\n  <a class=\"rs-button88\" href=\"#\">SECTOR 9 BBS</a>\n  <a class=\"rs-button88\" href=\"#\"><img src=\"valid-html.gif\" alt=\"Valid HTML 4.01\" width=\"88\" height=\"31\"></a>\n</div>"
    },
    {
      "id": "card-profile",
      "kind": "component",
      "label": "Profile card",
      "summary": "The member card: avatar, name, rank stars and a fields table.",
      "era": "The message-board member card beside every post: avatar, rank stars, and a Location/Interests table.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-card-profile",
          "role": "the member card shell"
        },
        {
          "name": "rs-card-profile__avatar",
          "role": "slot: the 80×80 avatar"
        },
        {
          "name": "rs-card-profile__name",
          "role": "slot: member name"
        },
        {
          "name": "rs-card-profile__rank",
          "role": "slot: rank stars line"
        },
        {
          "name": "rs-card-profile__fields",
          "role": "slot: the location/interests fields table"
        }
      ],
      "a11y": "The name is a real heading and the fields are a real table with th scope=\"row\" labels, so readers get proper label-value pairs.",
      "assets": [],
      "tags": [
        "content",
        "forum",
        "profile"
      ],
      "snippet": "<article class=\"rs-card-profile\">\n  <img class=\"rs-card-profile__avatar rs-avatar\" src=\"avatar.gif\" alt=\"Vector~Prime's avatar: a knight in mirrored shades\" width=\"80\" height=\"80\">\n  <h3 class=\"rs-card-profile__name\">Vector~Prime</h3>\n  <p class=\"rs-card-profile__rank\">\n    <span class=\"rs-stars\" role=\"img\" aria-label=\"Rank: 4 of 5 stars\">★★★★<span class=\"rs-stars__empty\">☆</span></span>\n    Station Commander\n  </p>\n  <table class=\"rs-card-profile__fields\">\n    <tr><th scope=\"row\">Location</th><td>Sector 9</td></tr>\n    <tr><th scope=\"row\">Interests</th><td>space sims, tracker music</td></tr>\n    <tr><th scope=\"row\">Homepage</th><td><a href=\"#\">vectorprime.example.net</a></td></tr>\n  </table>\n</article>"
    },
    {
      "id": "center",
      "kind": "component",
      "label": "Center",
      "summary": "Centers text and block children. Culturally load-bearing.",
      "era": "<center>, the most honest tag HTML ever had. Deprecated, never forgotten.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-center",
          "role": "text-align center; block children get auto margins"
        }
      ],
      "a11y": "None specific.",
      "assets": [],
      "tags": [
        "layout",
        "typography"
      ],
      "snippet": "<div class=\"rs-center\">\n  <h2>~ * ~ welcome ~ * ~</h2>\n  <p>Everything here is centered, as nature intended.</p>\n</div>"
    },
    {
      "id": "checkbox",
      "kind": "component",
      "label": "Checkbox",
      "summary": "The 13×13 sunken checkbox with a proper check glyph.",
      "era": "Windows 95's 13-by-13 checkbox, the exact square you ticked to join a mailing list forever.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-checkbox",
          "role": "restyled native checkbox: 13×13 sunken box, black check glyph"
        }
      ],
      "a11y": "Under the pixel paint sits the native input, keyboard toggling and screen-reader states are untouched, and the dotted focus ring lands on the label. Always wire the <label for>.",
      "assets": [],
      "tags": [
        "forms",
        "checkbox"
      ],
      "snippet": "<p>\n  <input class=\"rs-checkbox\" type=\"checkbox\" id=\"opt-news\" checked>\n  <label for=\"opt-news\">Notify me when this page is updated!</label>\n</p>\n<p>\n  <input class=\"rs-checkbox\" type=\"checkbox\" id=\"opt-midi\">\n  <label for=\"opt-midi\">I want the MIDI playlist too</label>\n</p>"
    },
    {
      "id": "cols",
      "kind": "component",
      "label": "Columns",
      "summary": "Equal columns for badge walls and link farms. 4 → 2 → 1, 3 → 1, 2 → 1 as the screen shrinks.",
      "era": "The links-page table: rows of 88×31 buttons and blue underlines, cellpadding 4.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-cols-2",
          "role": "two equal columns from vga up"
        },
        {
          "name": "rs-cols-3",
          "role": "three equal columns from svga up"
        },
        {
          "name": "rs-cols-4",
          "role": "four from svga, two from vga, one below"
        }
      ],
      "a11y": "None specific; column order is source order.",
      "assets": [],
      "tags": [
        "layout",
        "grid"
      ],
      "snippet": "<div class=\"rs-cols-2\">\n  <p>left column</p>\n  <p>right column</p>\n</div>\n\n<div class=\"rs-cols-3\">\n  <p>cool site #1</p>\n  <p>cool site #2</p>\n  <p>cool site #3</p>\n</div>"
    },
    {
      "id": "construction",
      "kind": "component",
      "label": "Under construction",
      "summary": "The permanently-temporary under-construction sign: a yellow board between caution bars.",
      "era": "The digging-worker barricade GIF that lived on every homepage, forever unfinished.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-construction",
          "role": "the sign: caution-striped bars around a yellow text board"
        },
        {
          "name": "rs-construction--bar",
          "role": "modifier: just the striped barrier, no text"
        }
      ],
      "a11y": "Ordinary text in a block; the caution stripes are decorative pseudo-elements. Say what's under construction in the text.",
      "assets": [],
      "tags": [
        "content",
        "decoration"
      ],
      "snippet": "<div class=\"rs-construction\">\n  This page is under construction!<br>\n  Come back soon (when dad fixes the scanner).\n</div>\n\n<div class=\"rs-construction rs-construction--bar\" aria-hidden=\"true\"></div>"
    },
    {
      "id": "container",
      "kind": "component",
      "label": "Container",
      "summary": "Width governor: 760px by default, centered, fluid below its max.",
      "era": "\"Best viewed at 800×600\", 760px is what actually fit that CRT after scrollbar and chrome.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-container",
          "role": "max-width 760px, centered"
        },
        {
          "name": "rs-container--wide",
          "role": "modifier: max-width 1000px"
        },
        {
          "name": "rs-container--fluid",
          "role": "modifier: full width"
        }
      ],
      "a11y": "None specific.",
      "assets": [],
      "tags": [
        "layout",
        "width"
      ],
      "snippet": "<div class=\"rs-container\">\n  <p>760 pixels of prime real estate, centered.</p>\n</div>\n\n<div class=\"rs-container rs-container--wide\">\n  <p>The deluxe 1000px lot, for portals with a lot to say.</p>\n</div>"
    },
    {
      "id": "counter",
      "kind": "component",
      "label": "Counter",
      "summary": "The sunken odometer shell for a hit counter; static digits work today, the hit-counter widget populates it later.",
      "era": "The odometer hit counter at the foot of the page, proudly proving 4,096 visitors since 1998.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-counter",
          "role": "sunken bezel, black bg, odometer digits"
        },
        {
          "name": "rs-counter__label",
          "role": "the \"visitors since …\" line"
        }
      ],
      "a11y": "One visually hidden sentence carries the count; the odometer digits are aria-hidden decoration. Never a live region.",
      "assets": [
        "counter-digits"
      ],
      "tags": [
        "content",
        "counter",
        "hits"
      ],
      "snippet": "<p class=\"rs-counter\">\n  <span class=\"rs-sr-only\">You are visitor 4,096.</span>\n  <span aria-hidden=\"true\">004096</span>\n  <span class=\"rs-counter__label\">visitors since March 1999</span>\n</p>"
    },
    {
      "id": "dialog",
      "kind": "component",
      "label": "Dialog",
      "summary": "The native dialog in system-alert costume: titlebar, icon, message and button row over a dithered backdrop.",
      "era": "The system alert: titlebar, 32×32 icon, OK and Cancel, the box the operating system sent when it needed you.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": "dialog",
      "classes": [
        {
          "name": "rs-dialog",
          "role": "native <dialog> in alert-box costume; 8×8 dither backdrop"
        },
        {
          "name": "rs-dialog__titlebar",
          "role": "titlebar; carries the id aria-labelledby points at"
        },
        {
          "name": "rs-dialog__body",
          "role": "icon slot plus message"
        },
        {
          "name": "rs-dialog__buttons",
          "role": "button row, a method=\"dialog\" form"
        },
        {
          "name": "rs-dialog--info",
          "role": "modifier: info icon"
        },
        {
          "name": "rs-dialog--warn",
          "role": "modifier: warning icon"
        },
        {
          "name": "rs-dialog--error",
          "role": "modifier: error icon"
        }
      ],
      "a11y": "Native <dialog> opened with showModal(): focus containment, Escape and the top layer come from the platform. aria-labelledby points at the titlebar text; the button row is a method=\"dialog\" form.",
      "assets": [
        "alert-info",
        "alert-warn",
        "alert-error"
      ],
      "tags": [
        "feedback",
        "dialog",
        "modal"
      ],
      "snippet": "<dialog class=\"rs-dialog rs-dialog--warn\" open aria-labelledby=\"dlg-del-title\">\n  <div class=\"rs-dialog__titlebar\" id=\"dlg-del-title\">Confirm delete</div>\n  <div class=\"rs-dialog__body\">\n    <p>Delete SPACETHEME.MID? You will have to hum it yourself.</p>\n  </div>\n  <form method=\"dialog\" class=\"rs-dialog__buttons\">\n    <button autofocus>OK</button>\n    <button value=\"cancel\">Cancel</button>\n  </form>\n</dialog>"
    },
    {
      "id": "dos",
      "kind": "component",
      "label": "DOS box",
      "summary": "A black terminal block on <pre>, with an optional C:\\> title strip.",
      "era": "Terminal cosplay from the textfiles web, black screen, gray Courier, ASCII banners, and cheat tables at the C:\\> prompt.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-dos",
          "role": "the terminal block on <pre>: black bg, gray mono, 2px sunken border"
        },
        {
          "name": "rs-dos__title",
          "role": "the C:\\> strip above the block"
        }
      ],
      "a11y": "Built on <pre>, so spacing and reading order survive exactly as typed. Give large ASCII art a short text alternative nearby, screen readers read every slash.",
      "assets": [],
      "tags": [
        "typography",
        "terminal",
        "mono"
      ],
      "snippet": "<div class=\"rs-dos__title\">C:\\HOMEPAGE&gt;</div>\n<pre class=\"rs-dos\">\ntype welcome.txt\n\nGreetings, visitor. You have reached the coolest\ncorner of this hard drive. Mind the ASCII dust.\n\n1 file(s) displayed, 0 regrets\n</pre>"
    },
    {
      "id": "error-page",
      "kind": "component",
      "label": "Error page",
      "summary": "The 404 furniture: a DOS box with the status line and a link back home.",
      "era": "The bare \"HTTP 404, File Not Found\" server page, redecorated by webmasters into an apology with a way home.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-error-page",
          "role": "the 404 furniture wrapper"
        },
        {
          "name": "rs-error-page__code",
          "role": "the status line, heading weight"
        }
      ],
      "a11y": "A page, not a widget: real <main>, the status line first in reading order, and a working link home. The sad floppy is decoration.",
      "assets": [
        "icon-floppy"
      ],
      "tags": [
        "feedback",
        "404",
        "page"
      ],
      "snippet": "<main class=\"rs-error-page\">\n  <pre class=\"rs-dos\"><span class=\"rs-error-page__code\">HTTP 404, File Not Found</span>\n\nThe file you asked for moved, got renamed,\nor never existed in the first place.</pre>\n  <p><a href=\"/\">&lt;&lt; Back to the homepage</a></p>\n</main>"
    },
    {
      "id": "fieldset",
      "kind": "component",
      "label": "Fieldset",
      "summary": "Native fieldset and legend wearing the groove border.",
      "era": "The Windows group box: a groove border with its label riding the line, standard issue in every 90s options dialog.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-fieldset",
          "role": "native fieldset + legend with the groove border"
        }
      ],
      "a11y": "The native <fieldset> and <legend> pair announces the group name with every control inside it, that grouping is the point, so never fake the legend with a heading.",
      "assets": [],
      "tags": [
        "forms",
        "grouping"
      ],
      "snippet": "<fieldset class=\"rs-fieldset\">\n  <legend>Mailing list</legend>\n  <p>\n    <label for=\"ml-email\">Your email:</label>\n    <input class=\"rs-input\" id=\"ml-email\" type=\"text\">\n  </p>\n  <p>\n    <input class=\"rs-checkbox\" type=\"checkbox\" id=\"ml-weekly\" checked>\n    <label for=\"ml-weekly\">Send me the weekly update</label>\n  </p>\n</fieldset>"
    },
    {
      "id": "figure",
      "kind": "component",
      "label": "Figure",
      "summary": "An image in a 2px outset frame with an optional sunken caption strip.",
      "era": "A scanned photo in an outset frame with a caption strip below, the \"my cat, 72dpi\" exhibit on every personal page.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-figure",
          "role": "2px outset frame around the image"
        },
        {
          "name": "rs-figure__caption",
          "role": "sunken caption strip below"
        }
      ],
      "a11y": "Native <figure>/<figcaption>: the caption describes the exhibit, the alt text still describes the image itself.",
      "assets": [],
      "tags": [
        "content",
        "image",
        "caption"
      ],
      "snippet": "<figure class=\"rs-figure\">\n  <img src=\"my-cat.jpg\" alt=\"A gray cat asleep on a beige keyboard\" width=\"240\" height=\"180\">\n  <figcaption class=\"rs-figure__caption\">Pixel, guarding the F5 key. Scanned at 72dpi like everything else.</figcaption>\n</figure>"
    },
    {
      "id": "font-scale",
      "kind": "component",
      "label": "Font scale",
      "summary": "The seven sanctioned text sizes, 10px to 48px, as utility classes.",
      "era": "The <font size=1..7> ladder at the browsers' own mapping, 10, 13, 16, 18, 24, 32, 48 pixels of pure 1997 hierarchy.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-font-1",
          "role": "10px (size=1): fine print, counters, footers"
        },
        {
          "name": "rs-font-2",
          "role": "13px (size=2): sans-theme body text"
        },
        {
          "name": "rs-font-3",
          "role": "16px (size=3): serif-theme body text"
        },
        {
          "name": "rs-font-4",
          "role": "18px (size=4): lead paragraphs, h4"
        },
        {
          "name": "rs-font-5",
          "role": "24px (size=5): section headings"
        },
        {
          "name": "rs-font-6",
          "role": "32px (size=6): welcome banners, h1"
        },
        {
          "name": "rs-font-7",
          "role": "48px (size=7): splash pages"
        }
      ],
      "a11y": "Pixel names, rem values underneath, browser zoom and user font-size overrides keep working. For contrast, steps 1-4 are body text; only 5 and up count as large (4 sneaks in when bold).",
      "assets": [],
      "tags": [
        "typography",
        "utilities",
        "sizes"
      ],
      "snippet": "<p class=\"rs-font-1\">size 1: the fine print under the hit counter</p>\n<p class=\"rs-font-2\">size 2: cozy sans body text</p>\n<p class=\"rs-font-3\">size 3: regular body text, business as usual</p>\n<p class=\"rs-font-5\">size 5: now we are heading somewhere</p>\n<p class=\"rs-font-7\">size 7: WELCOME!!</p>"
    },
    {
      "id": "footer",
      "kind": "component",
      "label": "Footer",
      "summary": "The page footer kit: last-updated line, 88×31 badge wall, and a mailto.",
      "era": "The page basement of 1999: a \"Last updated\" line, an 88×31 badge wall, and a mailto envelope that spun.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-footer",
          "role": "the footer kit wrapper"
        },
        {
          "name": "rs-footer__updated",
          "role": "the \"Last updated:\" line; pairs with the last-updated widget"
        },
        {
          "name": "rs-footer__badges",
          "role": "88×31 badge wall row"
        },
        {
          "name": "rs-footer__email",
          "role": "the mailto line"
        }
      ],
      "a11y": "A real <footer> landmark; badge links need proper accessible names, and the mailto shows the address as its own link text.",
      "assets": [
        "icon-mail"
      ],
      "tags": [
        "navigation",
        "footer",
        "page"
      ],
      "snippet": "<footer class=\"rs-footer\">\n  <p class=\"rs-footer__updated\">Last updated: March 14, 1999, fixed the guestbook, broke the counter.</p>\n  <div class=\"rs-footer__badges\">\n    <a class=\"rs-button88\" href=\"#\">MADE WITH RETROSTRAP</a>\n    <a class=\"rs-button88\" href=\"#\">BEST VIEWED WITH EYES</a>\n  </div>\n  <p class=\"rs-footer__email\">\n    Webmaster: <a href=\"mailto:webmaster@example.net\">webmaster@example.net</a>\n  </p>\n  <p>© 1997-1999. All rights reserved, whatever that means.</p>\n</footer>"
    },
    {
      "id": "form-table",
      "kind": "component",
      "label": "Form table",
      "summary": "The label:field two-column form grid, minus the table.",
      "era": "Every 1999 signup form was a two-column table, bold labels right-aligned against their fields. The table left; the look stayed.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-form-table",
          "role": "the grid: bold right-aligned labels, fields beside; stacks below vga"
        },
        {
          "name": "rs-form-error",
          "role": "the error message line under a field"
        }
      ],
      "a11y": "It is a grid, not a <table>, so screen readers meet label-then-field in source order. Keep <label for> on every control and tie each rs-form-error line to its field with aria-describedby.",
      "assets": [],
      "tags": [
        "forms",
        "layout",
        "grid"
      ],
      "snippet": "<form class=\"rs-form-table\">\n  <label for=\"ft-name\">Name:</label>\n  <input class=\"rs-input\" id=\"ft-name\" type=\"text\">\n\n  <label for=\"ft-email\">Email:</label>\n  <input class=\"rs-input\" id=\"ft-email\" type=\"text\" aria-invalid=\"true\" aria-describedby=\"ft-email-err\">\n  <p class=\"rs-form-error\" id=\"ft-email-err\">That does not look like an email address, friend.</p>\n\n  <label for=\"ft-home\">Homepage:</label>\n  <input class=\"rs-input\" id=\"ft-home\" type=\"text\" value=\"http://\">\n</form>"
    },
    {
      "id": "frames",
      "kind": "component",
      "label": "Frames",
      "summary": "Frameset cosplay: sticky side pane and main pane, each scrolling on its own, divided by a ridge.",
      "era": "Netscape 2.0 framesets, nav frame left, content frame right, minus the broken back button.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-frames",
          "role": "grid wrapper"
        },
        {
          "name": "rs-frames__side",
          "role": "sticky side pane (top strip below svga)"
        },
        {
          "name": "rs-frames__main",
          "role": "main pane"
        },
        {
          "name": "rs-frames--right",
          "role": "modifier: side pane on the right"
        }
      ],
      "a11y": "Side pane should be a <nav>, main pane a <main>. Real URLs, working back button, the parts framesets got wrong.",
      "assets": [],
      "tags": [
        "layout",
        "frames",
        "navigation"
      ],
      "snippet": "<div class=\"rs-frames\">\n  <nav class=\"rs-frames__side\" aria-label=\"Site\">\n    <a href=\"#\">home</a><br>\n    <a href=\"#\">my scope</a><br>\n    <a href=\"#\">guestbook</a>\n  </nav>\n  <main class=\"rs-frames__main\">\n    <p>The main pane scrolls on its own, like a proper frameset, except\n    bookmarks work and the back button tells the truth.</p>\n  </main>\n</div>"
    },
    {
      "id": "gradient-text",
      "kind": "component",
      "label": "Gradient text",
      "summary": "Two-stop gradient display text in fire, ice, and toxic flavors.",
      "era": "The WordArt welcome banner and the logo-mill headline GIF, a gradient poured into very large letters.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-gradient-text",
          "role": "two-stop gradient text, display sizes only"
        },
        {
          "name": "rs-gradient-text--fire",
          "role": "modifier: red to yellow"
        },
        {
          "name": "rs-gradient-text--ice",
          "role": "modifier: aqua to blue"
        },
        {
          "name": "rs-gradient-text--toxic",
          "role": "modifier: lime to teal"
        }
      ],
      "a11y": "Real text with a painted fill, keep it at display sizes, so both gradient stops clear large-text contrast against the theme background.",
      "assets": [],
      "tags": [
        "typography",
        "color",
        "headline"
      ],
      "snippet": "<h1 class=\"rs-gradient-text\">My Gradient Masterpiece</h1>\n<h2 class=\"rs-gradient-text rs-gradient-text--fire\">This Week's Hottest Files</h2>\n<h2 class=\"rs-gradient-text rs-gradient-text--ice\">The Chill-Out Lounge</h2>"
    },
    {
      "id": "hr",
      "kind": "component",
      "label": "Horizontal rule",
      "summary": "A wardrobe of horizontal rules for the semantic <hr>.",
      "era": "The divider-GIF drawer of 1998, braided ropes, chasing lights, rainbow bars, because every section break deserved jewelry.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-hr",
          "role": "base rule; the upgrade for a semantic <hr>"
        },
        {
          "name": "rs-hr--groove",
          "role": "modifier: the classic 3D groove"
        },
        {
          "name": "rs-hr--dotted",
          "role": "modifier: dotted line"
        },
        {
          "name": "rs-hr--rainbow",
          "role": "modifier: 4px banded rainbow bar"
        },
        {
          "name": "rs-hr--stars",
          "role": "modifier: tiled pixel-star divider"
        }
      ],
      "a11y": "Keep the semantic <hr>, assistive tech announces it as a separator, whatever costume it wears.",
      "assets": [
        "star-divider"
      ],
      "tags": [
        "typography",
        "divider"
      ],
      "snippet": "<p>Thanks for scrolling this far!</p>\n<hr class=\"rs-hr rs-hr--rainbow\">\n<p>My MIDI collection lives below, all seventeen files, hand-picked.</p>\n<hr class=\"rs-hr rs-hr--stars\">\n<p>This section is under construction. Aren't they all?</p>"
    },
    {
      "id": "input",
      "kind": "component",
      "label": "Text input",
      "summary": "The sunken single-line text input.",
      "era": "The white sunken fields of the guestbook form, name, email, homepage, waiting on every page worth visiting.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-input",
          "role": "sunken text input; invalid state gets the 1px red border"
        }
      ],
      "a11y": "Every field gets a <label for>, never a placeholder standing in for one. Invalid means a red border plus an rs-form-error line tied on with aria-describedby, so the message is more than a color.",
      "assets": [],
      "tags": [
        "forms",
        "input"
      ],
      "snippet": "<p>\n  <label for=\"gb-name\">Your name:</label>\n  <input class=\"rs-input\" id=\"gb-name\" type=\"text\">\n</p>\n<p>\n  <label for=\"gb-home\">Your homepage:</label>\n  <input class=\"rs-input\" id=\"gb-home\" type=\"text\" value=\"http://\">\n</p>"
    },
    {
      "id": "kbd",
      "kind": "component",
      "label": "Keycap",
      "summary": "A <kbd> dressed as a beveled keycap.",
      "era": "\"Press CTRL+D to bookmark this site!\", the era's favorite parting instruction, finally wearing real keycaps.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-kbd",
          "role": "keycap chrome on <kbd>: sunken bevel, mono, tight padding"
        }
      ],
      "a11y": "The element stays a semantic <kbd>, so keyboard input reads as keyboard input; the keycap is only chrome.",
      "assets": [],
      "tags": [
        "typography",
        "mono",
        "keyboard"
      ],
      "snippet": "<p>Press <kbd class=\"rs-kbd\">Ctrl</kbd> + <kbd class=\"rs-kbd\">D</kbd> to bookmark this page. You know you want to!</p>\n<p>Secret: try <kbd class=\"rs-kbd\">Up</kbd> <kbd class=\"rs-kbd\">Up</kbd> <kbd class=\"rs-kbd\">Down</kbd> <kbd class=\"rs-kbd\">Down</kbd> on the splash screen.</p>"
    },
    {
      "id": "layout",
      "kind": "component",
      "label": "Layout recipes",
      "summary": "Page-shape recipes via grid areas: sidebars, holy grail, portal thirds. Single column below svga.",
      "era": "Nested table layouts: nav column left, content right, banner on top, rebuilt as grid, no tables harmed.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-layout",
          "role": "grid wrapper"
        },
        {
          "name": "rs-layout--sidebar-left",
          "role": "recipe: 180px nav + main"
        },
        {
          "name": "rs-layout--sidebar-right",
          "role": "recipe: main + 180px rail"
        },
        {
          "name": "rs-layout--holy-grail",
          "role": "recipe: header / nav main rail / footer"
        },
        {
          "name": "rs-layout--three-col",
          "role": "recipe: the portal look, three honest thirds"
        },
        {
          "name": "rs-layout__header",
          "role": "area: header"
        },
        {
          "name": "rs-layout__nav",
          "role": "area: navigation column"
        },
        {
          "name": "rs-layout__main",
          "role": "area: content"
        },
        {
          "name": "rs-layout__rail",
          "role": "area: right rail"
        },
        {
          "name": "rs-layout__footer",
          "role": "area: footer"
        }
      ],
      "a11y": "Source order stays header, main, nav, rail, footer, readers first; the grid rearranges visually from svga up. Use real landmark elements for the areas.",
      "assets": [],
      "tags": [
        "layout",
        "grid",
        "responsive"
      ],
      "snippet": "<div class=\"rs-layout rs-layout--sidebar-left\">\n  <header class=\"rs-layout__header\">\n    <h1>My Site</h1>\n  </header>\n  <main class=\"rs-layout__main\">\n    <p>Content first in the source, even though the nav sits on the left.</p>\n  </main>\n  <nav class=\"rs-layout__nav\" aria-label=\"Site\">\n    <a href=\"#\">home</a><br>\n    <a href=\"#\">about me</a><br>\n    <a href=\"#\">links</a>\n  </nav>\n  <footer class=\"rs-layout__footer\">\n    <p>Last updated: yesterday, probably.</p>\n  </footer>\n</div>"
    },
    {
      "id": "list",
      "kind": "component",
      "label": "List",
      "summary": "A <ul> with themed pixel markers in place of the stock disc.",
      "era": "GeoCities' jeweled bullets, spinning balls, gems and tiny arrows doing the stock <li> disc's job with style.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-list",
          "role": "the themed <ul>; nested levels alternate glyphs"
        },
        {
          "name": "rs-list--stars",
          "role": "modifier: star bullets"
        },
        {
          "name": "rs-list--arrows",
          "role": "modifier: arrow bullets"
        },
        {
          "name": "rs-list--disks",
          "role": "modifier: floppy-disk bullets"
        },
        {
          "name": "rs-list--paws",
          "role": "modifier: paw-print bullets"
        }
      ],
      "a11y": "Markers are decoration on an honest <ul>: item counts and list semantics reach screen readers untouched.",
      "assets": [
        "bullet-star",
        "bullet-arrow",
        "bullet-disk",
        "bullet-paw"
      ],
      "tags": [
        "typography",
        "list",
        "bullets"
      ],
      "snippet": "<ul class=\"rs-list rs-list--stars\">\n  <li>my top five space games</li>\n  <li>cheat codes (tested, working!)</li>\n  <li>the screenshot gallery</li>\n</ul>\n\n<ul class=\"rs-list rs-list--paws\">\n  <li>photos of my cat</li>\n  <li>more photos of my cat</li>\n</ul>"
    },
    {
      "id": "loading",
      "kind": "component",
      "label": "Loading",
      "summary": "An hourglass with \"Loading, please wait…\"; the --bar variant adds an indeterminate progress bar.",
      "era": "The hourglass, promoted from cursor to page furniture: \"Loading, please wait…\" while the modem sang.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-loading",
          "role": "hourglass sprite plus waiting text"
        },
        {
          "name": "rs-loading--bar",
          "role": "modifier: wraps an indeterminate rs-progress"
        }
      ],
      "a11y": "The text carries the meaning; the hourglass is aria-hidden decoration and holds still under reduced motion.",
      "assets": [
        "hourglass"
      ],
      "tags": [
        "feedback",
        "loading",
        "motion"
      ],
      "snippet": "<p class=\"rs-loading\">Loading, please wait…</p>\n\n<div class=\"rs-loading rs-loading--bar\">\n  <p>Loading, please wait…</p>\n  <div class=\"rs-progress rs-progress--indeterminate\" role=\"progressbar\" aria-label=\"Loading\"></div>\n</div>"
    },
    {
      "id": "marquee",
      "kind": "component",
      "label": "Marquee",
      "summary": "Scrolling text at one of three sane speeds; pauses on hover and focus, stands still under reduced motion.",
      "era": "The <marquee> tag Internet Explorer 2.0 shipped in 1995, welcome banners crawling across a million homepages.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": "marquee",
      "classes": [
        {
          "name": "rs-marquee",
          "role": "the scrolling viewport; pauses on hover and focus-within"
        },
        {
          "name": "rs-marquee__content",
          "role": "the moving track (the enhancer clones it for a seamless loop)"
        }
      ],
      "a11y": "The text lives once in the DOM and reads in order; the enhancer's duplicate track is aria-hidden. Under prefers-reduced-motion it renders as static, fully-wrapped text and the pause button disappears (nothing to pause).",
      "assets": [],
      "tags": [
        "typography",
        "motion",
        "marquee"
      ],
      "snippet": "<div class=\"rs-marquee\" data-rs-marquee-speed=\"normal\">\n  <span class=\"rs-marquee__content\">\n    ★·.·´¯`·.·★ welcome to my homepage ★·.·´¯`·.·★ now with 30% more content ★·.·´¯`·.·★\n  </span>\n</div>"
    },
    {
      "id": "menu",
      "kind": "component",
      "label": "Menu",
      "summary": "A dropdown menu bar: nested list submenus open on hover and focus; the enhancer adds keyboard and tap.",
      "era": "The \"DHTML dropdown menu\" cut-and-pasted from a free-scripts site onto every portal of 2000.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": "menu",
      "classes": [
        {
          "name": "rs-menu",
          "role": "menu bar nav; nested ul submenus open on hover/focus-within"
        }
      ],
      "a11y": "Disclosure navigation, not role=\"menu\": submenu triggers are <button aria-expanded aria-controls>, submenus are plain lists of links. The enhancer adds the full keyboard map.",
      "assets": [],
      "tags": [
        "navigation",
        "dropdown",
        "menu"
      ],
      "snippet": "<nav class=\"rs-menu\" aria-label=\"Main menu\">\n  <ul>\n    <li><a href=\"#\">Home</a></li>\n    <li>\n      <button aria-expanded=\"false\" aria-controls=\"menu-files\">Files</button>\n      <ul id=\"menu-files\">\n        <li><a href=\"#\">Downloads</a></li>\n        <li><a href=\"#\">Skins</a></li>\n        <li><a href=\"#\">MIDI archive</a></li>\n      </ul>\n    </li>\n    <li><a href=\"#\">Guestbook</a></li>\n  </ul>\n</nav>"
    },
    {
      "id": "navbar",
      "kind": "component",
      "label": "Navbar",
      "summary": "A horizontal bar of bevel-chip links; the current page renders sunken.",
      "era": "The row of bevel-edged button links under the site logo, current page pressed in like a held key.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-navbar",
          "role": "horizontal row of bevel-chip links; wraps to two rows below svga, never a hamburger"
        }
      ],
      "a11y": "A <nav> landmark with an aria-label; the current page carries aria-current=\"page\", that is what renders it sunken.",
      "assets": [],
      "tags": [
        "navigation",
        "bar"
      ],
      "snippet": "<nav class=\"rs-navbar\" aria-label=\"Site\">\n  <a href=\"#\" aria-current=\"page\">Home</a>\n  <a href=\"#\">News</a>\n  <a href=\"#\">Downloads</a>\n  <a href=\"#\">Links</a>\n  <a href=\"#\">Guestbook</a>\n</nav>"
    },
    {
      "id": "note",
      "kind": "component",
      "label": "Note",
      "summary": "A yellow sticky note with a hard shadow, for asides and webmaster's notes.",
      "era": "The yellow \"webmaster's note\" box wedged into every 1999 update log, apologizing for the guestbook again.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-note",
          "role": "sticky note: #FFFF99 bg, olive border, hard 2px shadow"
        }
      ],
      "a11y": "Just styled content; mark it up as an <aside> when it truly is one. No role required.",
      "assets": [],
      "tags": [
        "content",
        "aside"
      ],
      "snippet": "<aside class=\"rs-note\">\n  <p><b>Webmaster's note:</b> the guestbook is down while I argue with the CGI script.\n  Sign the old one twice to compensate.</p>\n</aside>"
    },
    {
      "id": "page",
      "kind": "component",
      "label": "Page sheet",
      "summary": "The opaque content sheet that sits on the tiled or colored body.",
      "era": "The centered content table on a starfield background, the anatomy of every 1997 homepage.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-page",
          "role": "the sheet: content background, padding, bevel border from vga up"
        }
      ],
      "a11y": "Purely visual wrapper; carries no role. Put your landmarks inside it.",
      "assets": [],
      "tags": [
        "layout",
        "page"
      ],
      "snippet": "<div class=\"rs-page rs-container\">\n  <h1>Welcome to my homepage!</h1>\n  <p>You have reached the content sheet. The body behind it wears the wallpaper.</p>\n</div>"
    },
    {
      "id": "pagination",
      "kind": "component",
      "label": "Pagination",
      "summary": "A page-number row: current page bracketed, Prev/Next in angle brackets.",
      "era": "\"Pages: [1] 2 3 … Next>\", the forum thread pager, current page bracketed because you cannot click where you stand.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-pagination",
          "role": "the page link row; the current page is bracketed via aria-current"
        }
      ],
      "a11y": "<nav aria-label=\"Pages\"> of real links; the current page is plain text with aria-current=\"page\", bracketed by CSS, never a link to itself.",
      "assets": [],
      "tags": [
        "navigation",
        "pages"
      ],
      "snippet": "<nav class=\"rs-pagination\" aria-label=\"Pages\">\n  <span aria-current=\"page\">1</span>\n  <a href=\"#\">2</a>\n  <a href=\"#\">3</a>\n  <a href=\"#\">4</a>\n  <span>…</span>\n  <a href=\"#\">12</a>\n  <a href=\"#\">Next&gt;</a>\n</nav>"
    },
    {
      "id": "panel",
      "kind": "component",
      "label": "Panel",
      "summary": "The general-purpose box: groove border with a title riding the line, fieldset-style.",
      "era": "The groove-bordered group box with its label sitting on the line, system settings dialogs taught every homepage this shape.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-panel",
          "role": "the box: 2px groove border, content padding"
        },
        {
          "name": "rs-panel__title",
          "role": "title riding the border line, legend-style"
        },
        {
          "name": "rs-panel--raised",
          "role": "modifier: bevel out"
        },
        {
          "name": "rs-panel--sunken",
          "role": "modifier: bevel in"
        }
      ],
      "a11y": "Use a real heading for the title; when the box groups form controls, build it on <fieldset> with the title as <legend> instead.",
      "assets": [],
      "tags": [
        "content",
        "box",
        "fieldset"
      ],
      "snippet": "<section class=\"rs-panel\">\n  <h2 class=\"rs-panel__title\">About me</h2>\n  <p>Webmaster, age 15. I like space sims, MIDI files, and collecting 88×31 buttons.</p>\n</section>\n\n<div class=\"rs-panel rs-panel--sunken\">\n  <p>The sunken variant, for output wells and quiet asides.</p>\n</div>"
    },
    {
      "id": "progress",
      "kind": "component",
      "label": "Progress bar",
      "summary": "A segmented progress bar: sunken track, chunky accent blocks, optional marching indeterminate mode.",
      "era": "The file-copy dialog: a sunken track filling with chunky blue blocks, eight pixels at a time.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-progress",
          "role": "sunken track"
        },
        {
          "name": "rs-progress__bar",
          "role": "filled portion, segmented blocks"
        },
        {
          "name": "rs-progress--indeterminate",
          "role": "modifier: three blocks marching"
        }
      ],
      "a11y": "role=\"progressbar\" with aria-valuemin/max/now and an aria-label, as in the snippet; indeterminate mode omits aria-valuenow.",
      "assets": [],
      "tags": [
        "content",
        "progress",
        "feedback"
      ],
      "snippet": "<p>Copying SQ3D.ZIP (3 of 8 files)…</p>\n<div class=\"rs-progress\" role=\"progressbar\" aria-label=\"Copying files\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"60\">\n  <div class=\"rs-progress__bar\" style=\"width: 60%\"></div>\n</div>\n\n<p>Contacting server…</p>\n<div class=\"rs-progress rs-progress--indeterminate\" role=\"progressbar\" aria-label=\"Contacting server\"></div>"
    },
    {
      "id": "quote",
      "kind": "component",
      "label": "Forum quote",
      "summary": "The forum quote block: source strip, quoted body, and the signature divider.",
      "era": "phpBB 2's [quote] pyramid, \"Originally posted by\" strips stacked like sediment, the signature ruled off below the post.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-quote",
          "role": "the quote block: 1px sunken border; nesting flattens past three"
        },
        {
          "name": "rs-quote__source",
          "role": "silver header strip: Originally posted by …"
        },
        {
          "name": "rs-quote__body",
          "role": "the quoted text itself"
        },
        {
          "name": "rs-quote__sig",
          "role": "signature divider: the -- line, then up to three muted lines"
        }
      ],
      "a11y": "Put the quoted text in a real <blockquote>; the source strip sits outside it, so attribution never reads as quotation. Signatures stay under three lines, RFC 1855 manners, kept by CSS.",
      "assets": [],
      "tags": [
        "typography",
        "forum",
        "quote"
      ],
      "snippet": "<div class=\"rs-quote\">\n  <div class=\"rs-quote__source\">Originally posted by Webmaster</div>\n  <blockquote class=\"rs-quote__body\">\n    <p>The marquee stays. It is the soul of the page.</p>\n  </blockquote>\n</div>\n<p>Agreed! But only at 60 pixels per second -- we are not animals.</p>\n<p class=\"rs-quote__sig\">--<br>\n~*~ visit my homepage ~*~<br>\nproud member of three webrings and counting</p>"
    },
    {
      "id": "radio",
      "kind": "component",
      "label": "Radio button",
      "summary": "The 12×12 sunken pixel-circle radio; a dot appears when checked.",
      "era": "The site poll's round buttons, Windows 95 radios, whose 12-pixel \"circle\" was always secretly an octagon.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-radio",
          "role": "restyled native radio: 12×12 sunken pixel-circle, dot when checked"
        }
      ],
      "a11y": "Still a native radio: arrow keys walk the group and states announce themselves. Group the choices in a fieldset with a legend, and give every option its <label for>.",
      "assets": [],
      "tags": [
        "forms",
        "radio"
      ],
      "snippet": "<fieldset class=\"rs-fieldset\">\n  <legend>Site poll: best screen resolution?</legend>\n  <p>\n    <input class=\"rs-radio\" type=\"radio\" name=\"res\" id=\"res-640\">\n    <label for=\"res-640\">640x480 (classic)</label>\n  </p>\n  <p>\n    <input class=\"rs-radio\" type=\"radio\" name=\"res\" id=\"res-800\" checked>\n    <label for=\"res-800\">800x600 (the people's choice)</label>\n  </p>\n  <p>\n    <input class=\"rs-radio\" type=\"radio\" name=\"res\" id=\"res-1024\">\n    <label for=\"res-1024\">1024x768 (showoff)</label>\n  </p>\n</fieldset>"
    },
    {
      "id": "rainbow",
      "kind": "component",
      "label": "Rainbow text",
      "summary": "Per-character rainbow text via a banded gradient, for headlines only.",
      "era": "Rainbow headlines built one <font color> tag per letter, a whole evening in Notepad, now a single class.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-rainbow",
          "role": "banded per-character rainbow via background-clip; headlines only"
        }
      ],
      "a11y": "It stays plain, selectable text under the paint. Reserve it for 24px-and-up headlines, where the banded colors clear large-text contrast against the theme background.",
      "assets": [],
      "tags": [
        "typography",
        "color",
        "headline"
      ],
      "snippet": "<h1 class=\"rs-rainbow\">Welcome to my Rainbow Page!</h1>\n<p>Every letter colors itself now, in 1998 that was one font tag apiece.</p>"
    },
    {
      "id": "rollover",
      "kind": "component",
      "label": "Rollover",
      "summary": "The onmouseover swap, hide one thing, reveal another on hover or focus, in pure CSS.",
      "era": "onMouseOver image swaps: the first bit of interactivity most homepages ever shipped.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-rollover",
          "role": "the wrapper; give it its own accessible name"
        },
        {
          "name": "rs-rollover__off",
          "role": "shown at rest"
        },
        {
          "name": "rs-rollover__on",
          "role": "revealed on hover or focus"
        }
      ],
      "a11y": "The two halves are paint only; the wrapping link or button carries the accessible name. It swaps on focus as well as hover, so keyboard users see it too.",
      "assets": [],
      "tags": [
        "decoration",
        "link"
      ],
      "snippet": "<a class=\"rs-rollover\" href=\"#top\">\n  <span class=\"rs-rollover__off\">★ back to top ★</span>\n  <span class=\"rs-rollover__on\">&#9650; back to top &#9650;</span>\n</a>"
    },
    {
      "id": "select",
      "kind": "component",
      "label": "Select",
      "summary": "A native <select> in bevel chrome with a pixel down-arrow.",
      "era": "The guestbook's \"How did you find my page?\" dropdown, a real Windows combo box, right down to the arrow.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-select",
          "role": "native select in bevel chrome with the pixel down-arrow"
        }
      ],
      "a11y": "It stays a native <select>: keyboard, screen readers and phone pickers all work untouched. We only paint the box the arrow lives on.",
      "assets": [
        "select-button"
      ],
      "tags": [
        "forms",
        "select"
      ],
      "snippet": "<p>\n  <label for=\"found-via\">How did you find my page?</label>\n  <select class=\"rs-select\" id=\"found-via\">\n    <option>a webring</option>\n    <option>a search engine</option>\n    <option>a friend's links page</option>\n    <option>total accident (welcome!)</option>\n  </select>\n</p>"
    },
    {
      "id": "sidenav",
      "kind": "component",
      "label": "Sidenav",
      "summary": "The left nav list: pixel bullets, section headers, and visited links proudly purple.",
      "era": "The left-hand nav column of every frameset site: star bullets, bold section caps, links purple from visiting.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-sidenav",
          "role": "the nav list; visited-link purple lives here"
        },
        {
          "name": "rs-sidenav__section",
          "role": "bold caps section header"
        }
      ],
      "a11y": "A <nav> landmark with an aria-label wrapping real lists; the bullets are decoration, and links keep their visited state on purpose.",
      "assets": [
        "bullet-star"
      ],
      "tags": [
        "navigation",
        "sidebar",
        "links"
      ],
      "snippet": "<nav class=\"rs-sidenav\" aria-label=\"Site\">\n  <h3 class=\"rs-sidenav__section\">Main</h3>\n  <ul>\n    <li><a href=\"#\">home</a></li>\n    <li><a href=\"#\">news archive</a></li>\n    <li><a href=\"#\">about me</a></li>\n  </ul>\n  <h3 class=\"rs-sidenav__section\">Goodies</h3>\n  <ul>\n    <li><a href=\"#\">midi jukebox</a></li>\n    <li><a href=\"#\">cool links</a></li>\n    <li><a href=\"#\">webrings</a></li>\n  </ul>\n</nav>"
    },
    {
      "id": "skip",
      "kind": "component",
      "label": "Skip link",
      "summary": "A skip-to-content link, visually hidden until focused, first in the tab order.",
      "era": "\"Skip intro\", the most-clicked link of the splash-screen era, repurposed to jump keyboards straight to the content.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-skip",
          "role": "visually hidden until focused, then a bevel chip at the top left"
        }
      ],
      "a11y": "The first focusable element in <body>, pointing at #main; on focus it appears as a proper 3D button. Every page ships one.",
      "assets": [],
      "tags": [
        "navigation",
        "a11y",
        "focus"
      ],
      "snippet": "<a class=\"rs-skip\" href=\"#main\">Skip intro →</a>\n<main id=\"main\">\n  <p>The skip link is the first thing the keyboard reaches, and invisible until it does.</p>\n</main>"
    },
    {
      "id": "spacer",
      "kind": "component",
      "label": "Spacer",
      "summary": "A vertical spacer block on the spacing scale.",
      "era": "The 1×1 transparent spacer.gif, stretched into invisible struts. It held the old web together.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-spacer",
          "role": "block spacer, default 8px"
        },
        {
          "name": "rs-spacer--1",
          "role": "2px"
        },
        {
          "name": "rs-spacer--2",
          "role": "4px"
        },
        {
          "name": "rs-spacer--3",
          "role": "8px"
        },
        {
          "name": "rs-spacer--4",
          "role": "16px"
        },
        {
          "name": "rs-spacer--5",
          "role": "32px"
        }
      ],
      "a11y": "Decorative: mark it aria-hidden, as the snippet does.",
      "assets": [],
      "tags": [
        "layout",
        "spacing"
      ],
      "snippet": "<p>Some breathing room follows.</p>\n<div class=\"rs-spacer rs-spacer--4\" aria-hidden=\"true\"></div>\n<p>That was 16 pixels of pure nothing, in memory of spacer.gif.</p>"
    },
    {
      "id": "splash",
      "kind": "component",
      "label": "Splash",
      "summary": "A click-to-enter intro sheet, hidden until its enhancer decides to show it; the page never depends on it.",
      "era": "The click-to-enter splash page: a huge title, an [ ENTER ] link, and a webmaster certain you would wait.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": "splash",
      "classes": [
        {
          "name": "rs-splash",
          "role": "full-viewport click-to-enter sheet; hidden until the enhancer reveals it"
        },
        {
          "name": "rs-splash__enter",
          "role": "the [ ENTER ] button"
        }
      ],
      "a11y": "A native <dialog aria-label=\"Welcome\"> with an autofocused Enter button; the page behind stays in the DOM and is never hidden from assistive tech.",
      "assets": [],
      "tags": [
        "feedback",
        "intro",
        "splash"
      ],
      "snippet": "<dialog class=\"rs-splash\" aria-label=\"Welcome\" hidden>\n  <h1 class=\"rs-font-7\">STAR QUEST 3D</h1>\n  <p>a fan zone since 1998</p>\n  <button class=\"rs-btn rs-btn--large rs-splash__enter\" autofocus>[ ENTER ]</button>\n</dialog>\n<main>\n  <p>The actual page. It reads fine whether or not the splash ever appears.</p>\n</main>"
    },
    {
      "id": "spoiler",
      "kind": "component",
      "label": "Spoiler",
      "summary": "A details-based spoiler: content hidden until deliberately opened.",
      "era": "Black-on-black spoiler text on episode-guide pages, highlight to reveal how the season ends.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": "spoiler",
      "classes": [
        {
          "name": "rs-spoiler",
          "role": "details-based disclosure styled as the classic black bar"
        }
      ],
      "a11y": "Native <details>/<summary>: expanded/collapsed state is announced for free, and the hidden content stays out of the accessibility tree until opened.",
      "assets": [],
      "tags": [
        "content",
        "disclosure",
        "details"
      ],
      "snippet": "<details class=\"rs-spoiler\">\n  <summary>Spoiler: season finale</summary>\n  <p>The station was the ship all along. The captain knew since episode two.</p>\n</details>"
    },
    {
      "id": "stars",
      "kind": "component",
      "label": "Stars",
      "summary": "A display-only star rating: filled and empty pixel stars sized to the font.",
      "era": "The ★★★☆☆ verdict on every shareware download page, back when five stars meant something.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-stars",
          "role": "the star row, sized to the surrounding font"
        },
        {
          "name": "rs-stars__empty",
          "role": "the unfilled stars"
        }
      ],
      "a11y": "The glyph row is a single role=\"img\" with an aria-label like \"Rated 4 of 5 stars\", readers get the number, not five star characters.",
      "assets": [],
      "tags": [
        "content",
        "rating",
        "stars"
      ],
      "snippet": "<p>\n  Space Miner 2000,\n  <span class=\"rs-stars\" role=\"img\" aria-label=\"Rated 4 of 5 stars\">★★★★<span class=\"rs-stars__empty\">☆</span></span>\n  \"Better than the demo promised.\"\n</p>"
    },
    {
      "id": "statusbar",
      "kind": "component",
      "label": "Status bar",
      "summary": "A sunken status strip whose direct children become cells. With JS, a hovered or focused link's real destination previews in a cell laid over the bar.",
      "era": "The browser status bar every 1998 homepage scribbled welcomes into, rebuilt in-page, where it can't spoof a link.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": "statusbar",
      "classes": [
        {
          "name": "rs-statusbar",
          "role": "sunken strip; direct children become groove-divided cells"
        }
      ],
      "a11y": "Static text, no live region, a status bar that chattered would be worse than one that blinked. If a ticker docks here, the ticker brings its own screen-reader manners.",
      "assets": [],
      "tags": [
        "statusbar",
        "chrome",
        "feedback"
      ],
      "snippet": "<div class=\"rs-statusbar\">\n  <span>Document: Done</span>\n  <span>4 visitors online</span>\n  <span>Best viewed at 800x600</span>\n</div>"
    },
    {
      "id": "table",
      "kind": "component",
      "label": "Table",
      "summary": "Dressing for honest data tables: bordered cells, silver header row, stripe and mono-numeral modifiers.",
      "era": "The high-score table: silver header row, bordered cells, cellpadding=2 cellspacing=0, forever.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-table",
          "role": "base dressing: 1px cell borders, silver header row"
        },
        {
          "name": "rs-table--striped",
          "role": "modifier: alternating row backgrounds"
        },
        {
          "name": "rs-table--bordered",
          "role": "modifier: 2px sunken outer border"
        },
        {
          "name": "rs-table--data",
          "role": "modifier: mono numerals for data-heavy tables"
        },
        {
          "name": "rs-num",
          "role": "cell class: right-aligns number cells"
        }
      ],
      "a11y": "Data tables only: <caption>, <thead>, and th scope=\"col\"/\"row\" as in the snippet. The borders are CSS; the semantics stay honest.",
      "assets": [],
      "tags": [
        "content",
        "table",
        "data"
      ],
      "snippet": "<table class=\"rs-table rs-table--striped rs-table--data\">\n  <caption>Star Quest 3D, high scores, week 11</caption>\n  <thead>\n    <tr>\n      <th scope=\"col\">Pilot</th>\n      <th scope=\"col\" class=\"rs-num\">Score</th>\n      <th scope=\"col\" class=\"rs-num\">Sector</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">Vector~Prime</th>\n      <td class=\"rs-num\">1,048,576</td>\n      <td class=\"rs-num\">9</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">midnight_rider</th>\n      <td class=\"rs-num\">981,300</td>\n      <td class=\"rs-num\">8</td>\n    </tr>\n    <tr>\n      <th scope=\"row\">KHAOS99</th>\n      <td class=\"rs-num\">742,050</td>\n      <td class=\"rs-num\">7</td>\n    </tr>\n  </tbody>\n</table>"
    },
    {
      "id": "tabs",
      "kind": "component",
      "label": "Tabs",
      "summary": "Folder tabs over stacked panels; without the enhancer they are honest in-page links.",
      "era": "Manila folder tabs with the active lip merged into its page, the property-sheet look, brought to the web.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": "tabs",
      "classes": [
        {
          "name": "rs-tabs",
          "role": "the tabs wrapper"
        },
        {
          "name": "rs-tabs__tab",
          "role": "one folder lip"
        },
        {
          "name": "rs-tabs__panel",
          "role": "one content panel"
        },
        {
          "name": "rs-tabs__tab--active",
          "role": "modifier: active lip merged into its panel"
        }
      ],
      "a11y": "Unenhanced it is honest in-page links to headed, stacked sections, the snippet shows that shape. The enhancer adds role=tablist/tab/tabpanel, aria-selected and roving tabindex.",
      "assets": [],
      "tags": [
        "navigation",
        "tabs",
        "panels"
      ],
      "snippet": "<div class=\"rs-tabs\">\n  <a class=\"rs-tabs__tab rs-tabs__tab--active\" href=\"#tab-news\">News</a>\n  <a class=\"rs-tabs__tab\" href=\"#tab-files\">Files</a>\n  <a class=\"rs-tabs__tab\" href=\"#tab-links\">Links</a>\n\n  <section class=\"rs-tabs__panel\" id=\"tab-news\">\n    <h3>News</h3>\n    <p>Version 0.9 of the map pack is out. The lava level no longer eats saves.</p>\n  </section>\n  <section class=\"rs-tabs__panel\" id=\"tab-files\">\n    <h3>Files</h3>\n    <p>Everything zipped, everything under a floppy.</p>\n  </section>\n  <section class=\"rs-tabs__panel\" id=\"tab-links\">\n    <h3>Links</h3>\n    <p>Sites I actually visit, all six of them.</p>\n  </section>\n</div>"
    },
    {
      "id": "textarea",
      "kind": "component",
      "label": "Textarea",
      "summary": "The sunken multi-line field, for messages of substance.",
      "era": "The tall box under \"Your message:\", where a million guestbook greetings went, one heartfelt line at a time.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-textarea",
          "role": "sunken multi-line field; invalid state matches rs-input"
        }
      ],
      "a11y": "Label it with <label for> and size it with rows, the sunken bevel is paint, not policy. Invalid state matches rs-input: red border plus a linked rs-form-error line.",
      "assets": [],
      "tags": [
        "forms",
        "textarea"
      ],
      "snippet": "<p>\n  <label for=\"gb-msg\">Your message:</label><br>\n  <textarea class=\"rs-textarea\" id=\"gb-msg\" rows=\"4\" cols=\"40\">Cool page! Greetings from a fellow webmaster!</textarea>\n</p>"
    },
    {
      "id": "toolbar",
      "kind": "component",
      "label": "Toolbar",
      "summary": "A raised strip of buttons with groove separators that wraps instead of hiding.",
      "era": "The Back, Forward, Stop and Reload strip of every 1998 browser window, grooves between the button families.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-toolbar",
          "role": "the raised strip; wraps, never collapses into an overflow menu"
        },
        {
          "name": "rs-toolbar__sep",
          "role": "groove separator between button groups"
        }
      ],
      "a11y": "Buttons stay real buttons in the tab order; mark each separator aria-hidden, it is furniture. Overflow wraps to a second row, so nothing ever hides behind a menu.",
      "assets": [],
      "tags": [
        "toolbar",
        "buttons",
        "chrome"
      ],
      "snippet": "<div class=\"rs-toolbar\">\n  <button type=\"button\" class=\"rs-btn\">Back</button>\n  <button type=\"button\" class=\"rs-btn\">Forward</button>\n  <span class=\"rs-toolbar__sep\" aria-hidden=\"true\"></span>\n  <button type=\"button\" class=\"rs-btn\">Reload</button>\n  <button type=\"button\" class=\"rs-btn\">Home</button>\n  <span class=\"rs-toolbar__sep\" aria-hidden=\"true\"></span>\n  <button type=\"button\" class=\"rs-btn\">Music: off</button>\n</div>"
    },
    {
      "id": "tooltip",
      "kind": "component",
      "label": "Tooltip",
      "summary": "A small yellow tip box on hover and focus, fed by a data-rs-tip attribute.",
      "era": "The little yellow tip that popped up beside the cursor when you hovered a toolbar button in 1997.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-tooltip",
          "role": "trigger class; the tip text lives in data-rs-tip"
        }
      ],
      "a11y": "Shows on hover and keyboard focus alike, so the trigger must be focusable. Never essential content, the tip lives in a CSS pseudo-element.",
      "assets": [],
      "tags": [
        "content",
        "tooltip",
        "hover"
      ],
      "snippet": "<p>\n  The download is a\n  <a href=\"#\" class=\"rs-tooltip\" data-rs-tip=\"Zipped with maximum compression. Unzips to 4.2 MB.\">1.44 MB ZIP</a>\n  that fits on one floppy, naturally.\n</p>"
    },
    {
      "id": "top",
      "kind": "component",
      "label": "Top link",
      "summary": "The \"^ Top\" back-to-top link; instant jump, no smooth scroll.",
      "era": "The \"^ Top\" link sprinkled through every long FAQ, back when pages were tall and scroll wheels were optional.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-top",
          "role": "the back-to-top link idiom; jumps instantly"
        }
      ],
      "a11y": "A plain in-page link; href=\"#top\" falls back to the document top natively. The jump is instant, smooth scrolling is not used.",
      "assets": [],
      "tags": [
        "navigation",
        "link"
      ],
      "snippet": "<h3 id=\"faq-part-9\">Part 9: joystick trouble</h3>\n<p>…several screens of answers later…</p>\n<p><a class=\"rs-top\" href=\"#top\">^ Top</a></p>"
    },
    {
      "id": "typewriter",
      "kind": "component",
      "label": "Typewriter",
      "summary": "Courier text with letterspacing; the reveal modifier types it on.",
      "era": "The window.status typewriter loop that typed welcomes into the browser's status bar, promoted to the page, letter by letter.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-typewriter",
          "role": "Courier stack + 1px letterspacing"
        },
        {
          "name": "rs-typewriter--reveal",
          "role": "modifier: types on with steps(); instant under reduced motion"
        }
      ],
      "a11y": "The reveal is decoration over real text: under prefers-reduced-motion the line simply appears, fully typed. Screen readers always get the whole sentence.",
      "assets": [],
      "tags": [
        "typography",
        "motion",
        "mono"
      ],
      "snippet": "<h2 class=\"rs-typewriter rs-typewriter--reveal\">Initializing homepage . . .</h2>\n<p class=\"rs-typewriter\">All systems nominal. Enjoy your stay, visitor.</p>"
    },
    {
      "id": "webring-bar",
      "kind": "component",
      "label": "Webring bar",
      "summary": "The centered prev/random/next bar with a ring name line; static links until the webring widget fills them.",
      "era": "The webring bar bolted under the footer: Prev, Random, Next, and faith that surfers would keep the loop alive.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [
        {
          "name": "rs-webring-bar",
          "role": "centered prev/random/next bar"
        },
        {
          "name": "rs-webring-bar__name",
          "role": "the ring name line"
        }
      ],
      "a11y": "<nav aria-label=\"Webring\"> of plain links, fully functional as static HTML before the webring widget ever loads.",
      "assets": [],
      "tags": [
        "navigation",
        "webring"
      ],
      "snippet": "<nav class=\"rs-webring-bar\" aria-label=\"Webring\">\n  <p class=\"rs-webring-bar__name\">This site is part of the Midnight Starfield webring.</p>\n  <a href=\"#\">&lt;&lt; Prev</a>\n  <a href=\"#\">Random</a>\n  <a href=\"#\">Ring Home</a>\n  <a href=\"#\">Next &gt;&gt;</a>\n</nav>"
    },
    {
      "id": "window",
      "kind": "component",
      "label": "Window",
      "summary": "Window chrome: titlebar, control buttons, body and status bar; dragging arrives with the windows widget.",
      "era": "Gray window chrome, gradient titlebar, _ □ ✕ in the corner, the 1998 desktop, quoted verbatim.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": "window",
      "classes": [
        {
          "name": "rs-window",
          "role": "window chrome shell"
        },
        {
          "name": "rs-window__titlebar",
          "role": "titlebar with the two-stop fade"
        },
        {
          "name": "rs-window__title",
          "role": "title text; carries the id aria-labelledby points at"
        },
        {
          "name": "rs-window__controls",
          "role": "control button group"
        },
        {
          "name": "rs-window__body",
          "role": "window content area"
        },
        {
          "name": "rs-window__statusbar",
          "role": "sunken status cells"
        },
        {
          "name": "rs-window--inactive",
          "role": "modifier: grayed titlebar"
        }
      ],
      "a11y": "A <section role=\"dialog\"> (non-modal, no aria-modal) labelled via aria-labelledby to the titlebar text; controls are real buttons with aria-labels. Never traps focus, it is furniture, not a modal.",
      "assets": [],
      "tags": [
        "content",
        "chrome",
        "desktop"
      ],
      "snippet": "<section class=\"rs-window\" role=\"dialog\" aria-labelledby=\"win-readme-title\">\n  <div class=\"rs-window__titlebar\">\n    <span class=\"rs-window__title\" id=\"win-readme-title\">README.TXT</span>\n    <span class=\"rs-window__controls\">\n      <button aria-label=\"Minimize\">_</button>\n      <button aria-label=\"Maximize\">□</button>\n      <button aria-label=\"Close\">✕</button>\n    </span>\n  </div>\n  <div class=\"rs-window__body\">\n    <p>Thank you for downloading Star Quest 3D. Unzip everything into one folder before running SETUP.</p>\n  </div>\n  <div class=\"rs-window__statusbar\"><span>Ready</span></div>\n</section>"
    }
  ],
  "widgets": [
    {
      "id": "clock",
      "kind": "widget",
      "label": "Clock",
      "summary": "An LED clock, refreshed once a second.",
      "era": "The JavaScript clock tucked into the corner of the 'enhanced' homepage.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-clock",
          "role": "applied to the host"
        }
      ],
      "options": [
        {
          "name": "format",
          "type": "enum",
          "values": [
            "12",
            "24"
          ],
          "default": "24"
        },
        {
          "name": "seconds",
          "type": "bool",
          "default": true
        },
        {
          "name": "label",
          "type": "string",
          "note": "a caption under the digits"
        }
      ],
      "events": [],
      "a11y": "The visible digits are aria-hidden; a screen reader reads an sr-only <time>, updated once a minute, not every tick. A changing digit is content, not motion, so reduced motion changes nothing.",
      "assets": [],
      "tags": [
        "informative",
        "toybox"
      ],
      "snippet": "<span data-rs-widgets=\"clock\" data-rs-clock-format=\"12\" data-rs-clock-label=\"server time\"></span>"
    },
    {
      "id": "countdown",
      "kind": "widget",
      "label": "Countdown",
      "summary": "An LED countdown to a target date and time.",
      "era": "\"Only 47 days until the year 2000!\" on a million homepages.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-countdown",
          "role": "applied to the host"
        }
      ],
      "options": [
        {
          "name": "to",
          "type": "datetime",
          "note": "ISO datetime; required"
        },
        {
          "name": "done",
          "type": "string",
          "note": "message shown at zero"
        }
      ],
      "events": [
        "rs:countdown:done"
      ],
      "a11y": "The digits are aria-hidden; a screen reader hears an sr-only remaining-time line updated once a minute. A changing digit is content, so reduced motion keeps the count.",
      "assets": [],
      "tags": [
        "informative",
        "toybox"
      ],
      "snippet": "<span data-rs-widgets=\"countdown\" data-rs-countdown-to=\"2000-01-01T00:00:00Z\"></span>"
    },
    {
      "id": "crt",
      "kind": "widget",
      "label": "CRT overlay",
      "summary": "Scanlines and a vignette, with an optional (off-by-default) flicker.",
      "era": "The look of a page seen through a tube monitor at 2 a.m.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-crt-overlay",
          "role": "the fixed scanline overlay"
        }
      ],
      "options": [
        {
          "name": "vignette",
          "type": "bool",
          "default": true
        },
        {
          "name": "flicker",
          "type": "bool",
          "default": false,
          "note": "capped at 2 changes/sec, tiny opacity delta; force-disabled under reduced motion"
        }
      ],
      "events": [],
      "a11y": "Decoration: aria-hidden, pointer-events:none. Flicker stays far below the photosensitivity threshold and never runs under prefers-reduced-motion.",
      "assets": [],
      "tags": [
        "decoration",
        "toybox"
      ],
      "snippet": "<body data-rs-widgets=\"crt\">"
    },
    {
      "id": "cursor-trail",
      "kind": "widget",
      "label": "Cursor trail",
      "summary": "A little pool of sprites that chase your pointer.",
      "era": "The DHTML trail scripts and Comet Cursor of the late-90s 'enhanced' homepage.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "fine",
      "classes": [],
      "options": [
        {
          "name": "variant",
          "type": "enum",
          "values": [
            "dots",
            "stars",
            "sparkle",
            "hearts",
            "bubbles",
            "image"
          ],
          "default": "stars"
        },
        {
          "name": "count",
          "type": "int",
          "default": 8,
          "note": "max 16"
        },
        {
          "name": "size",
          "type": "px",
          "default": 12,
          "note": "max 24"
        },
        {
          "name": "color",
          "type": "color",
          "note": "legal palette; default theme accent"
        },
        {
          "name": "image",
          "type": "url",
          "note": "for variant=image"
        }
      ],
      "events": [],
      "a11y": "Decoration: the layer is aria-hidden and pointer-events:none. Never spawns under prefers-reduced-motion, and no-ops on touch.",
      "assets": [],
      "tags": [
        "decoration",
        "pointer",
        "toybox"
      ],
      "snippet": "<!-- add the toybox script, then declare the widget on any element -->\n<body data-rs-widgets=\"cursor-trail\" data-rs-cursor-trail-variant=\"stars\" data-rs-cursor-trail-count=\"10\">"
    },
    {
      "id": "dvd",
      "kind": "widget",
      "label": "Bouncing logo",
      "summary": "A logo that bounces around the viewport, waiting for the corner hit.",
      "era": "The DVD screensaver logo that hypnotized a generation.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-dvd",
          "role": "the bouncing logo chip (when the widget creates its own)"
        }
      ],
      "options": [
        {
          "name": "target",
          "type": "selector",
          "note": "an element to bounce; otherwise a default chip"
        },
        {
          "name": "text",
          "type": "string",
          "note": "text for the default chip"
        },
        {
          "name": "speed",
          "type": "enum",
          "values": [
            "slow",
            "normal",
            "fast"
          ],
          "default": "normal"
        }
      ],
      "events": [
        "rs:dvd:corner"
      ],
      "a11y": "Decoration: aria-hidden, pointer-events:none. Under prefers-reduced-motion it parks in a corner and holds still.",
      "assets": [],
      "tags": [
        "decoration",
        "toybox"
      ],
      "snippet": "<body data-rs-widgets=\"dvd\">"
    },
    {
      "id": "fortune",
      "kind": "widget",
      "label": "Fortune floppy",
      "summary": "Shows an old-web fortune; click for another.",
      "era": "The fortune cookie of the command line, pinned to a homepage.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-fortune__quote",
          "role": "the current fortune line, added by the widget"
        }
      ],
      "options": [
        {
          "name": "list",
          "type": "json-array",
          "note": "custom fortunes via an inline application/json block, not a data attribute"
        }
      ],
      "events": [
        "rs:fortune:next"
      ],
      "a11y": "Plain text and a real button. The pick is seeded off the clock, so no Math.random.",
      "assets": [],
      "tags": [
        "informative",
        "toybox"
      ],
      "snippet": "<div class=\"rs-panel\" data-rs-widgets=\"fortune\"></div>"
    },
    {
      "id": "guestbook",
      "kind": "widget",
      "label": "Guestbook",
      "summary": "Renders guestbook entries and wires the sign form.",
      "era": "The comment section's gentle ancestor: a form, a list, and social obligation.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-guestbook__entries",
          "role": "the rendered entry list"
        }
      ],
      "options": [
        {
          "name": "src",
          "type": "url",
          "note": "endpoint or JSON file; or an inline <script type=application/json>"
        }
      ],
      "events": [
        "rs:content"
      ],
      "a11y": "Every entry renders as text nodes only, injected markup shows as literal text, never runs. A missing endpoint shows a friendly note. Honeypot and minimum-time traps guard an existing form.",
      "assets": [],
      "tags": [
        "informative",
        "toybox"
      ],
      "snippet": "<section data-rs-widgets=\"guestbook\" data-rs-guestbook-src=\"guestbook.json\">\n  <form action=\"mailto:me@example.com\" method=\"post\">…the rs- form fields…</form>\n</section>"
    },
    {
      "id": "hit-counter",
      "kind": "widget",
      "label": "Hit counter",
      "summary": "The odometer visitor counter: static, local, or api-backed.",
      "era": "The CGI counter that proved your page had visitors, in a pre-analytics world.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-counter",
          "role": "the odometer shell the widget renders into"
        }
      ],
      "options": [
        {
          "name": "mode",
          "type": "enum",
          "values": [
            "static",
            "local",
            "api"
          ],
          "default": "static"
        },
        {
          "name": "value",
          "type": "int",
          "default": 1337,
          "note": "for static mode"
        },
        {
          "name": "src",
          "type": "url",
          "note": "for api mode; POST returns { count }"
        },
        {
          "name": "digits",
          "type": "int",
          "default": 6,
          "note": "zero-pad width; the label line is your own rs-counter__label markup"
        }
      ],
      "events": [],
      "a11y": "Announced once to a screen reader as ordinary text, never on a tick. The api contract counts hits, never people, no identifiers stored (Decency Law).",
      "assets": [
        "counter-digits"
      ],
      "tags": [
        "informative",
        "toybox"
      ],
      "snippet": "You are visitor\n<span data-rs-widgets=\"hit-counter\" data-rs-hit-counter-mode=\"local\"></span>"
    },
    {
      "id": "jukebox",
      "kind": "widget",
      "label": "Jukebox",
      "summary": "A playlist audio player in window chrome. Never autoplays.",
      "era": "The MIDI player tucked in the corner, but this one waits for your click.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-jukebox",
          "role": "applied to the host, alongside rs-window"
        }
      ],
      "options": [
        {
          "name": "tracks",
          "type": "json-array",
          "note": "each { title, src }; attribute JSON or an inline JSON block"
        },
        {
          "name": "loop",
          "type": "bool",
          "default": false
        },
        {
          "name": "src",
          "type": "url",
          "note": "a fallback audio URL: plays when a track has no src, or alone as a one-track player; never fetched as a playlist"
        }
      ],
      "events": [
        "rs:jukebox:track"
      ],
      "a11y": "NEVER autoplays, there is no autoplay option (Decency Law). Fully keyboard-operable; the fake EQ is aria-hidden and holds still under reduced motion.",
      "assets": [],
      "tags": [
        "informative",
        "audio",
        "toybox"
      ],
      "snippet": "<div data-rs-widgets=\"jukebox\">\n  <script type=\"application/json\">{\"tracks\":[{\"title\":\"Hangar Bay\",\"src\":\"audio/hangar.ogg\"}]}</script>\n</div>"
    },
    {
      "id": "konami",
      "kind": "widget",
      "label": "Konami code",
      "summary": "The cheat code, and a 30-second party mode.",
      "era": "Up up down down left right left right B A: the most-typed secret on Earth.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "any",
      "classes": [],
      "options": [
        {
          "name": "href",
          "type": "url",
          "note": "navigate to a secret page on the code, instead of party mode"
        },
        {
          "name": "words",
          "type": "string",
          "note": "comma-separated secret words that also trigger the egg when typed"
        }
      ],
      "events": [],
      "a11y": "Loading the widget adds Retrostrap.konami.on(seq, fn) and .trigger(). Party mode composes only non-flashing widgets and stays in budget; under reduced motion it declines with a small note. A ten-tap gesture is the touch alternative.",
      "assets": [],
      "tags": [
        "decoration",
        "easter-egg",
        "toybox"
      ],
      "snippet": "<body data-rs-widgets=\"konami\">\n<!-- or give it a secret page: data-rs-konami-href=\"secret.html\" -->"
    },
    {
      "id": "kugeln",
      "kind": "widget",
      "label": "Kugeln",
      "summary": "Shiny 3D spheres that trail or orbit your pointer, in a rainbow of colours.",
      "era": "The spinning-ball DHTML cursor toys of around 2000, coloured orbs that chased the mouse across a homepage.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "fine",
      "classes": [],
      "options": [
        {
          "name": "colors",
          "type": "enum",
          "values": [
            "rainbow",
            "red",
            "gold",
            "green",
            "cyan",
            "blue",
            "purple",
            "pink",
            "silver"
          ],
          "default": "rainbow"
        },
        {
          "name": "count",
          "type": "int",
          "default": 7,
          "note": "max 12"
        },
        {
          "name": "size",
          "type": "px",
          "default": 20,
          "note": "8-32"
        },
        {
          "name": "mode",
          "type": "enum",
          "values": [
            "trail",
            "orbit"
          ],
          "default": "trail"
        }
      ],
      "events": [],
      "a11y": "Decoration: the layer is aria-hidden and pointer-events:none. Never spawns under prefers-reduced-motion, and no-ops on touch.",
      "assets": [
        "kugeln-red",
        "kugeln-gold",
        "kugeln-green",
        "kugeln-cyan",
        "kugeln-blue",
        "kugeln-purple",
        "kugeln-pink",
        "kugeln-silver"
      ],
      "tags": [
        "decoration",
        "pointer",
        "toybox"
      ],
      "snippet": "<!-- add the toybox script, then declare the widget on any element -->\n<!-- colors: rainbow (default) or one of red gold green cyan blue purple pink silver -->\n<body data-rs-widgets=\"kugeln\" data-rs-kugeln-colors=\"rainbow\" data-rs-kugeln-count=\"7\" data-rs-kugeln-mode=\"trail\">"
    },
    {
      "id": "last-updated",
      "kind": "widget",
      "label": "Last updated",
      "summary": "Prints the document's last-modified date, era-style.",
      "era": "\"Last updated: 04/17/1998\", the proudest line on any homepage.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-last-updated",
          "role": "applied to the host"
        }
      ],
      "options": [
        {
          "name": "prefix",
          "type": "string",
          "default": "Last updated:"
        },
        {
          "name": "date",
          "type": "string",
          "note": "pin this date verbatim instead of the file's last-modified time"
        },
        {
          "name": "format",
          "type": "enum",
          "values": [
            "long",
            "short"
          ],
          "default": "long"
        },
        {
          "name": "locale",
          "type": "string",
          "note": "defaults to the page lang"
        }
      ],
      "events": [],
      "a11y": "Static text; nothing to rule on. Hides itself if the browser reports a garbage date.",
      "assets": [],
      "tags": [
        "informative",
        "toybox"
      ],
      "snippet": "<p class=\"rs-footer__updated\" data-rs-widgets=\"last-updated\"></p>"
    },
    {
      "id": "neko",
      "kind": "widget",
      "label": "Neko (Gif the cat)",
      "summary": "A pixel cat that chases your cursor, patrols an element edge, or dozes.",
      "era": "The desktop-cat lineage (Neko/oneko), on the web since the late 90s. Gif is our own, drawn from a blank grid.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-neko",
          "role": "the cat sprite the widget creates"
        }
      ],
      "options": [
        {
          "name": "skin",
          "type": "enum",
          "values": [
            "gif",
            "calico",
            "void"
          ],
          "default": "gif"
        },
        {
          "name": "behavior",
          "type": "enum",
          "values": [
            "chase",
            "patrol",
            "sleepy"
          ],
          "default": "chase"
        },
        {
          "name": "target",
          "type": "selector",
          "note": "the element to patrol the top edge of"
        },
        {
          "name": "speed",
          "type": "enum",
          "values": [
            "slow",
            "normal",
            "fast"
          ],
          "default": "normal"
        },
        {
          "name": "src",
          "type": "string",
          "note": "override the sprite sheet URL (defaults to the skin's)"
        }
      ],
      "events": [
        "rs:neko:sleep",
        "rs:neko:wake"
      ],
      "a11y": "Decoration: aria-hidden, pointer-events:none. Under prefers-reduced-motion the cat stays, sitting still. Chase falls back to patrol on touch. Two cats per page, maximum.",
      "assets": [
        "neko-gif",
        "neko-calico",
        "neko-void"
      ],
      "tags": [
        "decoration",
        "mascot",
        "toybox"
      ],
      "snippet": "<!-- Gif patrols the top edge of the masthead -->\n<body data-rs-widgets=\"neko\" data-rs-neko-skin=\"gif\"\n      data-rs-neko-behavior=\"patrol\" data-rs-neko-target=\"#masthead\">"
    },
    {
      "id": "pixel-pet",
      "kind": "widget",
      "label": "Pixel pet",
      "summary": "A little desktop pet in the corner that you feed, and that remembers you between visits.",
      "era": "The virtual pets of the early-2000s web, a creature that lived in the corner of your page.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [],
      "options": [
        {
          "name": "name",
          "type": "string",
          "default": "Blobby"
        },
        {
          "name": "color",
          "type": "enum",
          "values": [
            "green",
            "blue",
            "pink",
            "gold"
          ],
          "default": "green"
        },
        {
          "name": "pace",
          "type": "int",
          "note": "seconds until fully hungry; default 300"
        }
      ],
      "events": [
        "rs:pixel-pet:fed",
        "rs:pixel-pet:gold"
      ],
      "a11y": "The pet is a labelled button you can feed with the keyboard; its mood is shown as text. State persists in localStorage. Appears (static) under prefers-reduced-motion.",
      "assets": [
        "pixel-pet-green",
        "pixel-pet-blue",
        "pixel-pet-pink",
        "pixel-pet-gold"
      ],
      "tags": [
        "companion",
        "toybox",
        "localstorage"
      ],
      "snippet": "<!-- add the toybox script, then declare the pet anywhere; it lives in a corner -->\n<!-- color: green (default) · blue · pink · gold -->\n<body data-rs-widgets=\"pixel-pet\" data-rs-pixel-pet-name=\"Blobby\" data-rs-pixel-pet-color=\"green\">"
    },
    {
      "id": "smilies",
      "kind": "widget",
      "label": "Smilies",
      "summary": "Turns :) and friends into pixel emoticons.",
      "era": "The forum smiley pack, type the code, see the face, running since the phpBB age.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-smiley",
          "role": "each emoticon image the parser inserts"
        }
      ],
      "options": [
        {
          "name": "scope",
          "type": "selector",
          "note": "the subtree to parse; defaults to the host"
        },
        {
          "name": "src",
          "type": "string",
          "note": "override the smilies pack image URL (same-origin)"
        }
      ],
      "events": [],
      "a11y": "Each image's alt is the original typed code, so copy-paste and screen readers round-trip. The parser skips code, kbd, inputs, and any [data-rs-no-smilies] region.",
      "assets": [
        "smilies"
      ],
      "tags": [
        "informative",
        "text",
        "toybox"
      ],
      "snippet": "<!-- codes in this element become pixel faces: :) ;) :D -->\n<div data-rs-widgets=\"smilies\">Welcome!! :) Sign my guestbook ;)</div>"
    },
    {
      "id": "snowfall",
      "kind": "widget",
      "label": "Snowfall",
      "summary": "Falling snow (or hearts, or confetti) on a canvas.",
      "era": "The JavaScript snow script every winter homepage installed in December.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-snowfall",
          "role": "the canvas overlay the widget creates"
        }
      ],
      "options": [
        {
          "name": "density",
          "type": "enum",
          "values": [
            "light",
            "normal",
            "blizzard"
          ],
          "default": "normal",
          "note": "or a number, capped at 100"
        },
        {
          "name": "glyph",
          "type": "enum",
          "values": [
            "star",
            "heart",
            "leaf",
            "confetti"
          ],
          "note": "default is a pixel flake"
        },
        {
          "name": "speed",
          "type": "enum",
          "values": [
            "slow",
            "normal",
            "fast"
          ],
          "default": "normal"
        },
        {
          "name": "wind",
          "type": "number",
          "default": 0,
          "note": "-2 to 2"
        },
        {
          "name": "melt",
          "type": "bool",
          "default": true
        }
      ],
      "events": [],
      "a11y": "Decoration: canvas is aria-hidden, pointer-events:none. Never mounts under prefers-reduced-motion; the shared loop pauses when the tab is hidden.",
      "assets": [],
      "tags": [
        "decoration",
        "canvas",
        "seasonal",
        "toybox"
      ],
      "snippet": "<body data-rs-widgets=\"snowfall\" data-rs-snowfall-density=\"light\">"
    },
    {
      "id": "sparkle",
      "kind": "widget",
      "label": "Sparkle",
      "summary": "Twinkles at the pointer, or ambient glitter on a zone.",
      "era": "The glitter-graphics and sparkle-trail scene of the kawaii and shrine pages.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "any",
      "classes": [],
      "options": [
        {
          "name": "mode",
          "type": "enum",
          "values": [
            "pointer",
            "ambient",
            "both"
          ],
          "default": "pointer"
        },
        {
          "name": "trigger",
          "type": "enum",
          "values": [
            "move",
            "click"
          ],
          "default": "move"
        },
        {
          "name": "palette",
          "type": "enum",
          "values": [
            "gold",
            "ice",
            "rainbow",
            "theme"
          ],
          "default": "theme"
        },
        {
          "name": "color",
          "type": "color",
          "note": "one legal color; overrides palette"
        },
        {
          "name": "zone",
          "type": "selector",
          "note": "ambient area; defaults to the host"
        }
      ],
      "events": [],
      "a11y": "Decoration: aria-hidden, pointer-events:none, silent under prefers-reduced-motion.",
      "assets": [],
      "tags": [
        "decoration",
        "toybox"
      ],
      "snippet": "<body data-rs-widgets=\"sparkle\" data-rs-sparkle-mode=\"both\" data-rs-sparkle-palette=\"gold\">"
    },
    {
      "id": "starfield",
      "kind": "widget",
      "label": "Starfield",
      "summary": "Warp-speed stars on a canvas.",
      "era": "The flying-through-space screensaver, reborn as a page background.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-starfield",
          "role": "the canvas overlay the widget creates"
        }
      ],
      "options": [
        {
          "name": "density",
          "type": "enum",
          "values": [
            "sparse",
            "normal",
            "dense"
          ],
          "default": "normal",
          "note": "or a number, capped at 200"
        },
        {
          "name": "speed",
          "type": "enum",
          "values": [
            "slow",
            "normal",
            "fast"
          ],
          "default": "normal"
        },
        {
          "name": "layers",
          "type": "int",
          "default": 2,
          "note": "1 to 3 parallax layers"
        }
      ],
      "events": [],
      "a11y": "Decoration: aria-hidden, pointer-events:none. Under prefers-reduced-motion it becomes a still sprinkle of stars, the look without the flight.",
      "assets": [],
      "tags": [
        "decoration",
        "canvas",
        "toybox"
      ],
      "snippet": "<body data-rs-widgets=\"starfield\">"
    },
    {
      "id": "ticker",
      "kind": "widget",
      "label": "Ticker",
      "summary": "A scrolling status-bar text ticker.",
      "era": "The window.status scrollers that welcomed you to a thousand homepages.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-ticker",
          "role": "applied to the host while it scrolls"
        }
      ],
      "options": [
        {
          "name": "messages",
          "type": "json-array",
          "note": "the lines to scroll"
        },
        {
          "name": "source",
          "type": "selector",
          "note": "read <li>s from this element instead"
        },
        {
          "name": "speed",
          "type": "enum",
          "values": [
            "slow",
            "normal",
            "fast"
          ],
          "default": "normal"
        },
        {
          "name": "dock",
          "type": "enum",
          "values": [
            "bottom",
            "none"
          ],
          "default": "none"
        },
        {
          "name": "separator",
          "type": "string",
          "default": "+++"
        }
      ],
      "events": [],
      "a11y": "Informative: the messages are ordinary readable text (never aria-live). Pauses on hover and focus. Under prefers-reduced-motion it swaps the message every 5s instead of scrolling.",
      "assets": [],
      "tags": [
        "informative",
        "toybox"
      ],
      "snippet": "<p class=\"rs-statusbar\" data-rs-widgets=\"ticker\"\n   data-rs-ticker-messages='[\"Welcome to my page!!\",\"Sign my guestbook\"]'></p>"
    },
    {
      "id": "transitions",
      "kind": "widget",
      "label": "Page transitions",
      "summary": "Page-to-page wipes via the View Transitions API.",
      "era": "The PowerPoint-meets-Internet-Explorer scene change, tamed.",
      "since": "0.1.0",
      "stability": "experimental",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "decorative",
      "pointer": "any",
      "classes": [],
      "options": [
        {
          "name": "style",
          "type": "enum",
          "values": [
            "wipe",
            "box-in",
            "box-out",
            "dissolve",
            "checker"
          ],
          "default": "wipe"
        }
      ],
      "events": [],
      "a11y": "Pure progressive enhancement: where the API is unsupported, navigation is instant and unbroken. The API honors reduced motion on its own; the widget also skips under it.",
      "assets": [],
      "tags": [
        "decoration",
        "navigation",
        "toybox"
      ],
      "snippet": "<body data-rs-widgets=\"transitions\">"
    },
    {
      "id": "webring",
      "kind": "widget",
      "label": "Webring",
      "summary": "Fills an rs-webring-bar from a ring's JSON, computing prev/random/next.",
      "era": "Sites holding hands in a circle, curated by a ringmaster.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-webring-bar",
          "role": "the host the widget fills with links"
        }
      ],
      "options": [
        {
          "name": "src",
          "type": "url",
          "note": "the ring JSON; or an inline <script type=application/json>"
        }
      ],
      "events": [],
      "a11y": "Plain links, fully functional as static HTML. Works from an inline ring with no service at all.",
      "assets": [],
      "tags": [
        "informative",
        "navigation",
        "toybox"
      ],
      "snippet": "<div class=\"rs-webring-bar\" data-rs-widgets=\"webring\" data-rs-webring-src=\"ring.json\"></div>"
    },
    {
      "id": "windows",
      "kind": "widget",
      "label": "Window manager",
      "summary": "Makes rs-window elements draggable, with click-to-front (or dispatch rs:window:raise) and a five-window cap.",
      "era": "The draggable fake-window scripts of the DHTML era.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": true,
      "jsEnhancer": null,
      "motion": "informative",
      "pointer": "any",
      "classes": [
        {
          "name": "rs-window",
          "role": "windows marked data-rs-window=floating become draggable"
        }
      ],
      "options": [],
      "events": [
        "rs:window:focus"
      ],
      "a11y": "Drag by the titlebar with mouse, touch, or the keyboard (focus the titlebar, arrow to move, Shift for fine steps, Home to reset). Dragging is disabled below the svga breakpoint, where windows render in flow. Five windows maximum.",
      "assets": [],
      "tags": [
        "informative",
        "chrome",
        "toybox"
      ],
      "snippet": "<body data-rs-widgets=\"windows\">\n  <div class=\"rs-window\" data-rs-window=\"floating\">…titlebar + body…</div>"
    }
  ],
  "themes": [
    {
      "id": "bevel",
      "kind": "theme",
      "label": "Bevel",
      "summary": "The system desktop: teal, silver chrome, navy titlebars, Tahoma.",
      "era": "Windows 2000 rendered as a website, every dialog you ever clicked OK on.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "Every body-text pair passes WCAG AA (validated by scripts/contrast.mjs). Bevel edges carry a 1px dark outer border so controls read against the desktop.",
      "assets": [],
      "tags": [
        "theme"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/bevel.css\">\n<html data-rs-theme=\"bevel\">"
    },
    {
      "id": "bubble",
      "kind": "theme",
      "label": "Bubble",
      "summary": "Candy-plastic 2001: white windows on a pinstriped aqua desktop, gel buttons, and traffic-light window controls.",
      "era": "The glossy translucent desktop of the early 2000s, homaged in hard-edged, web-safe blue.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "Every body-text pair passes WCAG AA (validated by scripts/contrast.mjs). Links stay underlined; the gloss is a hard-stop gradient, so nothing pulses.",
      "assets": [],
      "tags": [
        "theme"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/bubble.css\">\n<html data-rs-theme=\"bubble\">"
    },
    {
      "id": "classic",
      "kind": "theme",
      "label": "Classic",
      "summary": "The 1996 browser default: silver page, white sheet, Times, blue links.",
      "era": "The unstyled Netscape look worn with pride, a university professor's homepage.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "Ships in the core tokens; no theme file needed. Every body-text pair is AAA. Links stay underlined.",
      "assets": [],
      "tags": [
        "theme",
        "default"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/classic.css\">\n<html data-rs-theme=\"classic\">"
    },
    {
      "id": "cosmic",
      "kind": "theme",
      "label": "Cosmic",
      "summary": "A starry deep-purple void: pink and cyan neon on aubergine, over a tiled starfield.",
      "era": "The 'space' personal page dressed in nebula, midnight's dreamier cousin.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "All body-text pairs sit above AA; scripts/contrast.mjs gates them in CI. Links stay underlined.",
      "assets": [
        "tile-stars"
      ],
      "tags": [
        "theme"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/cosmic.css\">\n<html data-rs-theme=\"cosmic\">"
    },
    {
      "id": "highcontrast",
      "kind": "theme",
      "label": "High contrast",
      "summary": "Accessibility mode, 1998 spirit: black on white, larger text, an unmissable focus ring. Every pair clears AAA.",
      "era": "The 'high visibility' accessibility pages that took legibility seriously before it was cool.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "Built for maximum legibility: AAA on every body-text pair, body text a step larger, a 3px focus ring.",
      "assets": [],
      "tags": [
        "theme",
        "accessibility"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/highcontrast.css\">\n<html data-rs-theme=\"highcontrast\">"
    },
    {
      "id": "kawaii",
      "kind": "theme",
      "label": "Kawaii",
      "summary": "Pastel shrine: bubblegum page, tiled hearts, Comic Sans, hot-pink scalloped panels.",
      "era": "The glitter-and-hearts fanpage every 2001 diary kept, bows and all.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "Every body-text pair passes WCAG AA (validated by scripts/contrast.mjs). Links on the tinted page darken to a tinted pair automatically, and stay underlined.",
      "assets": [
        "tile-hearts"
      ],
      "tags": [
        "theme"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/kawaii.css\">\n<html data-rs-theme=\"kawaii\">"
    },
    {
      "id": "midnight",
      "kind": "theme",
      "label": "Midnight",
      "summary": "GeoCities after dark: black page, tiled starfield, neon text, yellow headings.",
      "era": "The black-with-neon cool page every 1998 teenager aspired to build.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "The docs/08 contrast matrix clears AA for every body pair here. Links keep their underlines.",
      "assets": [
        "tile-stars"
      ],
      "tags": [
        "theme"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/midnight.css\">\n<html data-rs-theme=\"midnight\">"
    },
    {
      "id": "newspaper",
      "kind": "theme",
      "label": "Newspaper",
      "summary": "The 1997 broadsheet: ink on newsprint, serif columns, a black masthead. Grayscale but for the links.",
      "era": "The web portal and online-newspaper look, all gravitas and a serif headline.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "Ink on newsprint reads well by construction: every body pair passes WCAG AA, checked by scripts/contrast.mjs. Links stay underlined.",
      "assets": [],
      "tags": [
        "theme"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/newspaper.css\">\n<html data-rs-theme=\"newspaper\">"
    },
    {
      "id": "phosphor",
      "kind": "theme",
      "label": "Phosphor",
      "summary": "Green terminal on black: Courier, scanlines welcome, rs-dos blends in.",
      "era": "A VT220 you can browse, textfiles, zines, and hacker cool.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "Green on black clears WCAG AA on every body pair; the contrast gate keeps it that way. Pair with rs-crt only; the flicker variant stays off.",
      "assets": [],
      "tags": [
        "theme"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/phosphor.css\">\n<html data-rs-theme=\"phosphor\">"
    },
    {
      "id": "y2k",
      "kind": "theme",
      "label": "Y2K",
      "summary": "Millennium chrome: dark navy dither page, banded silver panels, Impact headings.",
      "era": "The turn-of-the-century tech portal, brushed metal, cyan glow, install CDs.",
      "since": "0.1.0",
      "stability": "stable",
      "requiresJs": false,
      "jsEnhancer": null,
      "classes": [],
      "a11y": "Every body-text pair passes WCAG AA (validated by scripts/contrast.mjs). The blue accent is chrome only; body links take a silver-safe blue on the sheet and a bright cyan on the dark page, always underlined.",
      "assets": [
        "tile-dither-navy"
      ],
      "tags": [
        "theme"
      ],
      "snippet": "<!-- load the theme after the core stylesheet -->\n<link rel=\"stylesheet\" href=\"dist/retrostrap.min.css\">\n<link rel=\"stylesheet\" href=\"dist/themes/y2k.css\">\n<html data-rs-theme=\"y2k\">"
    }
  ]
}
