{
  "version": "1.8.0",
  "base": "/kit/1.8.0/",
  "authoring": {
    "css": "/kit/1.8.0/tacksheet.css",
    "js": "/kit/1.8.0/tacksheet.js",
    "themeAttribute": "data-tk-theme"
  },
  "files": [
    {
      "path": "/kit/1.8.0/tacksheet.css",
      "bytes": 179350,
      "sha256": "f2f493f510a81a3cc5d6b532c43ab2e9965cf59054db8824ebae7293bd4468fb",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/tacksheet.js",
      "bytes": 214278,
      "sha256": "40cfbab23ead8be26c7e0f4bdde53270b2288c03315ee34f9b7c22fd724bdf84",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/tacksheet-base.css",
      "bytes": 140846,
      "sha256": "77cea7480118d802cfbcb569f8112613c633acaad0a9bc91e00462d446594f93",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/answers.js",
      "bytes": 35948,
      "sha256": "cfc5428de1c8ad9be91268f183930e4f1aae85a09b83712dfd65651215badfb3",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/canvas.js",
      "bytes": 52842,
      "sha256": "4c957649578e0834e30507098e451e947fd5db1114e574fa31d484c16d0560aa",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/charts.js",
      "bytes": 76006,
      "sha256": "932d7f41c68f2f3cdf78da1ab1fb2561a9f74adf5265ef97d447c44c1cabc95a",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/maps.js",
      "bytes": 461631,
      "sha256": "970e09fa6f034d2c90746deb4082c569ab6f7129f74e724ac9201fd23008a010",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/three-kit.js",
      "bytes": 60341,
      "sha256": "e0bec6865d9021c256ca9c0037e9152588c7c4fec75fe8f3c13303f953857402",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/echarts.min.js",
      "bytes": 1030855,
      "sha256": "e84270bd0cd5bdf60fefc26d00c2a391cb2e81f4d26a7a9ee16185a54773a3cf",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/three.min.js",
      "bytes": 607784,
      "sha256": "f34446bf875b5fb0dcd93819ffe1d9e182d46634ee855f5d904c6c4ac7cdbc95",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/GLTFLoader.js",
      "bytes": 103311,
      "sha256": "b2edda923572c73e6b30b139756d182c87e2c34d022845c6e067a9a0a9544e01",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/OrbitControls.js",
      "bytes": 26660,
      "sha256": "b4c6e53f98538535b11fd6655627d47fb5877b3ad972568bff0a7026c4b6d5c4",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/PointerLockControls.js",
      "bytes": 3443,
      "sha256": "e37e140fa36586c16fd7ef0534c6b3ecf03d8daf44919ce97cf64f02555927ba",
      "contentType": "text/javascript; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/us-states.geojson",
      "bytes": 260075,
      "sha256": "9d7a7baff9be1f6a5226ebb7358d7fcbc5b8a2aa31af79b8266aa384be65ebda",
      "contentType": "application/json; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/world-countries.geojson",
      "bytes": 201346,
      "sha256": "42986659879c2a27f44970b4d035cd133c8d03ab5f8d848bec1fe47dfda85b60",
      "contentType": "application/json; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/sample.glb",
      "bytes": 72828,
      "sha256": "ae93f03c9168f28691a90b97f49e4c00e5165c638707f4efe61e3efcb4f6a76b",
      "contentType": "model/gltf-binary"
    },
    {
      "path": "/kit/1.8.0/docs.html",
      "bytes": 247027,
      "sha256": "7a96490f9ca4a375821a61dd58b480b6018a0b9f969b5c8739d806b5d231f010",
      "contentType": "text/html; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/fonts/newsreader.woff2",
      "bytes": 58084,
      "sha256": "62981321d9a3cc7a61a73792729043703fd6112da86e8ec848bb57f088578757",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/archivo.woff2",
      "bytes": 34928,
      "sha256": "8f704806dbedeaaeca334b11ec348bc3ac3a439d6431544b3afb54f534ee4967",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/archivo-black-400.woff2",
      "bytes": 18604,
      "sha256": "25f33e61cf995abd6be62931cf03bf427286259177b43618cc410ee0157cfd30",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/ibm-plex-mono-400.woff2",
      "bytes": 14708,
      "sha256": "08949f728dc52d528e69b1667d15c89a5686a4ee9a296ff90983985f99c380f7",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/ibm-plex-mono-500.woff2",
      "bytes": 14888,
      "sha256": "01d285447409c8a588692162439a038b8cbd7871309ee20267b0d2d91c6e8e22",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/kalam-400.woff2",
      "bytes": 22336,
      "sha256": "954410601a823f37e219f7930b7446f86afa15621326a7078d56fb9c910135cb",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/textures/paper.webp",
      "bytes": 22728,
      "sha256": "722152b3389aa47c49088706d4304a3d8533d0d158f58bdcb05b65786d83d05f",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/textures/riso.webp",
      "bytes": 15046,
      "sha256": "6404a58a1fb26d73dd31d63f4edf6033d88e36ef7a574525c226a3c6a8e1626c",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/textures/felt.webp",
      "bytes": 28440,
      "sha256": "f19417f5f60fa30702afceb88b47f42939e381913e1fe03154dcf91ef6f27128",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/textures/speck.svg",
      "bytes": 1438,
      "sha256": "c7d8ba2ad838fb8dd804860535486acd9d4910949b8fc72ec338c01ee98304a8",
      "contentType": "image/svg+xml"
    },
    {
      "path": "/kit/1.8.0/textures/fibre.svg",
      "bytes": 24297,
      "sha256": "9a04e46e9379c2d974b2b6112b5c33b5e68e12421be0739e6b42143d80e421bb",
      "contentType": "image/svg+xml"
    },
    {
      "path": "/kit/1.8.0/textures/grain.webp",
      "bytes": 63086,
      "sha256": "6b787220581df87a177dd528cebf88c9a5b454f203cf65ff2d384e0982daeb7d",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/textures/stamp-wear.png",
      "bytes": 51729,
      "sha256": "f79a23d6046b4dac2140631a64f057b04b4f0af45430c6dad26fdf32b1f6e061",
      "contentType": "image/png"
    },
    {
      "path": "/kit/1.8.0/textures/torn-top.png",
      "bytes": 2657,
      "sha256": "18b881470c9cb166dffad9cf855a74604be506a938bfc406ee42d1eb9c2e51cd",
      "contentType": "image/png"
    },
    {
      "path": "/kit/1.8.0/styles/aero.css",
      "bytes": 104072,
      "sha256": "534eca12d07e5151a4f7f62682b8e664455d0bb89c341b6eff6d9236fb52634f",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/broadsheet.css",
      "bytes": 50326,
      "sha256": "45f9510ab6d03bd39b6b6fd6aa8aee0485d123101bc75d645e8aa4c6e591c8a4",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/brutal.css",
      "bytes": 55138,
      "sha256": "65984006167a6b15f9d9960dc69464e515a871c8be4f14d8c25fb3791caeff78",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/comic.css",
      "bytes": 69000,
      "sha256": "630571aa34a864e2330ef3db8850541e787714ae5bea18c70278b3a488075c08",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/gallery.css",
      "bytes": 44301,
      "sha256": "8ad5bdb0567bc8f16b21176faedb4e101cea374fc6066f289247f3e6e876016e",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/geocities.css",
      "bytes": 64044,
      "sha256": "ad578c229bf5bd8979303c965d34a5d6cec6bd4f4d963e7ddee07ef97d7142f0",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/glossy.css",
      "bytes": 46633,
      "sha256": "4695601f53b7325f3bebd1a97d9c5defbef8c6e891b252d559f5724479fd0c68",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/keynote.css",
      "bytes": 66839,
      "sha256": "c6e772ee0ec6e931dc84956234b66a17d21c82f46be73f264be14cb77a5d9ecc",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/launch.css",
      "bytes": 56092,
      "sha256": "35c84e3c753b2b059f00f436f095b63a2aae75c0002ae8ee7a33a7c07b529f94",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/magazine.css",
      "bytes": 85669,
      "sha256": "f7c998d04d0c51678f53a6d165534ce9a811ba589f1adaeda59332ee3d479b6b",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/studio.css",
      "bytes": 56666,
      "sha256": "913e0e4b316385873352cb56477e0714e2a38011f09054fb510fa80eb1db62ff",
      "contentType": "text/css; charset=utf-8"
    },
    {
      "path": "/kit/1.8.0/styles/launch/horizon.webp",
      "bytes": 5592,
      "sha256": "da176dbda97b626f39345225ee1ba4cb0e97481d42dfec7e0d2999fd3732e20c",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/ribbon.webp",
      "bytes": 8886,
      "sha256": "bdbdbcb70c03ef9366c47a546dc69ef07df827ada8c39221a3eb435bf319a587",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/chroma.webp",
      "bytes": 5210,
      "sha256": "460216e02193021f73caf3f591d2852978c7f7af975c3444a930462ba9e9f64f",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/current.webp",
      "bytes": 6278,
      "sha256": "3c7027f0f065116ad39a29ba1ccb41ba6881e0e696f1c3966c9498e749752d92",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/arc.webp",
      "bytes": 5788,
      "sha256": "5e89362a0076420494b8b88285c2321893e0b3b4e8cbc4016903f77e49ad2e35",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/eclipse.webp",
      "bytes": 5712,
      "sha256": "ad24bcaeaae67834fccb6e096e7027b217ff0acfe51dab48a2d7d6a21bcad59c",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/club.webp",
      "bytes": 9880,
      "sha256": "a9e16f817e35c6fa5313323fbd961e1555351673de6acf250774a6c96f6989eb",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/prism.webp",
      "bytes": 11700,
      "sha256": "4815f5145b10b2f19b0697b1324c954a22d852b414a291041ba6f504a94d4568",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/nebula.webp",
      "bytes": 7220,
      "sha256": "b7002a5a157ff77f69bf237b98909b43d8315f0499919618494d632ebb11a1da",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/styles/launch/silk.webp",
      "bytes": 6380,
      "sha256": "c4a0ed7840c6baf734c277b672d120f8c2d987b8f96c51b2fd1981be90845183",
      "contentType": "image/webp"
    },
    {
      "path": "/kit/1.8.0/fonts/anybody-100-900.woff2",
      "bytes": 56888,
      "sha256": "041c675622919a7a380210851ecb00be81458a886441dab13269e1339b492354",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/bangers-400.woff2",
      "bytes": 23528,
      "sha256": "04f54ff42ea2b8fc50b4259c29c60b1e05e606b769c2fb541c98624346c9767c",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/bodoni-moda-400-900-italic.woff2",
      "bytes": 53756,
      "sha256": "b737328c13b92784ee78d99e62e3bcc38eeef0f2dba41d107637b3acea46781f",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/bodoni-moda-400-900.woff2",
      "bytes": 46260,
      "sha256": "c032219ef636f7b4932f7d1dc91bdab602c970f090770b546bea4effaab3463c",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/bricolage-grotesque-200-800.woff2",
      "bytes": 131548,
      "sha256": "a756882d8a7802ebd4663b4544a62ebedafc7227c1c159ab95eea61393af3f8e",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/comic-neue-400.woff2",
      "bytes": 19572,
      "sha256": "b7753f30f8bd4925cf48a2ffe936c52ff1f16fe441f8c0245acf046296ac590f",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/comic-neue-700.woff2",
      "bytes": 19244,
      "sha256": "13e9f12e3a0239dcf740e1c7ff37be1cce9b87ead0ad9bce838da1202ac9f91c",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/cormorant-garamond-300-700-italic.woff2",
      "bytes": 39260,
      "sha256": "6f2f5c3b1abc3d0bb035a927f66a90ca873f94fc31c4966c8d024142c2036e55",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/cormorant-garamond-300-700.woff2",
      "bytes": 37640,
      "sha256": "d80df8ff5aecd299a61549f9e29ab1ed0b9b05f4ea71d50fe978e07d5240b235",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/geist-100-900.woff2",
      "bytes": 29400,
      "sha256": "19f9c92546aa300c312235e3125af1b81394d8db9a4bc4a425cd5b641d2d54e1",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/geist-mono-400-600.woff2",
      "bytes": 23128,
      "sha256": "684ad5b531f81d43c1e8c7038262d5db7cdc1f68006e04d6c7769efa8d33c8cc",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/hanken-grotesk-100-900-italic.woff2",
      "bytes": 35592,
      "sha256": "bb432642d7e97d11bd8f7adbcb79dc69b772211c9b6eae5251a969496674d299",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/hanken-grotesk-100-900.woff2",
      "bytes": 34704,
      "sha256": "e9201eddf1d41d0b62253295d869ce3cf65768f7102b797f02c7f8c876b4a9d5",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/instrument-serif-400-italic.woff2",
      "bytes": 22128,
      "sha256": "5a51946dfffa82972bc98745359c46761515641fda557c25116459a9f83da4a7",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/instrument-serif-400.woff2",
      "bytes": 21032,
      "sha256": "5eb09b5ac0e28b67c2f041c8ba6d244604ca0c0980d65912ab2d47fed84ddc31",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/inter-400-700.woff2",
      "bytes": 48256,
      "sha256": "3100e775e8616cd2611beecfa23a4263d7037586789b43f035236a2e6fbd4c62",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/inter-tight-400-900.woff2",
      "bytes": 44872,
      "sha256": "77fefe8ca19b9f69b5284832c519e0493127c1f091f0a8936884be7721c4e618",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/jost-300-700.woff2",
      "bytes": 26576,
      "sha256": "7726a5cd6f3c0e876c028ea2a643d45f7aad4b0f164b70966c669f4a4668f4b9",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/libre-caslon-display-400.woff2",
      "bytes": 24236,
      "sha256": "84267e937417f10dff37df818c5ef7f1e9f2e6a703e03c2dd286d3320af53269",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/libre-caslon-text-400-italic.woff2",
      "bytes": 28020,
      "sha256": "20eac03c053229ce982a337fce41f12e42e18d33c4e29d8e89188ef4fa970425",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/libre-caslon-text-400.woff2",
      "bytes": 24972,
      "sha256": "29310efc1007f608549e29ff78fa5ce8dffe0c1aa82c21b082d1de9dfc7334f0",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/libre-caslon-text-700.woff2",
      "bytes": 25260,
      "sha256": "2819c345e64c715cc4235a5e9c3576351d28b4c0bbbeb934fcbd50ceab0406a0",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/libre-franklin-100-900.woff2",
      "bytes": 29292,
      "sha256": "52e605852c72cc2df1473df1fb7f26a3a0be58e31835bc8f33f29b1918c82cbd",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/noto-serif-display-100-900-italic.woff2",
      "bytes": 91024,
      "sha256": "e76bae99e265491d1f639c17e8d9a11efbb1a0abe2e8f4a69697e862c7beddc1",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/noto-serif-display-100-900.woff2",
      "bytes": 76372,
      "sha256": "5d546c5fdaef9f032d5f5f5c6550ece4ee9e3ff5c1a30057fc60ec6e9b1da3b5",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/nunito-400-900.woff2",
      "bytes": 39128,
      "sha256": "ba344451eab25b217a165363b1982048a5e5830a0daf36577973955a04cac793",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/tinos-400-italic.woff2",
      "bytes": 18756,
      "sha256": "55fe58e551b07fc6586e4e632d10ebf50339fe1f0f4ec97cb55a2abc7d18ff79",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/tinos-400.woff2",
      "bytes": 17848,
      "sha256": "18505b57c8a016326da164a1e9a14e8d1c5081ae78e9fe7b57bb66a330ea4ef9",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/tinos-700-italic.woff2",
      "bytes": 18868,
      "sha256": "6b199a846d3766e7d35961162b6dc399cda36ff60d3f26ef022be685b609425e",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/tinos-700.woff2",
      "bytes": 18900,
      "sha256": "4132bff0c1156d037289c3372cab59f16e4b0967b71d6cd219ce67619d02de73",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/unifrakturmaguntia-400.woff2",
      "bytes": 26512,
      "sha256": "a467466874b50cd9ffbe10e5caccd9b261f2bc2252bcfa7d160c744ed9da6f15",
      "contentType": "font/woff2"
    },
    {
      "path": "/kit/1.8.0/fonts/LICENSES-styles.txt",
      "bytes": 9132,
      "sha256": "8c8ab4bd8880ea9f4f01f9b8fa6b582eb605daffd1de159f0ab1c32a6d2193f7",
      "contentType": "text/plain; charset=utf-8"
    }
  ],
  "themes": [
    {
      "name": "riso",
      "description": "Default. Riso stock printed in two inks (cobalt and vermilion, plum where they overprint) with misregistered headings, paper grain, a halftone bloom and kraft rules. The page column sits on a translucent reading panel (--tk-panel; transparent turns it off)."
    },
    {
      "name": "riso-night",
      "description": "Riso on deep navy stock with grain: cream type, sunflower links, vermilion marks and a cornflower second ink. Tape, slips, stamps and prints stay cream paper. The page column sits on a translucent navy reading panel."
    },
    {
      "name": "auto",
      "description": "Follows the reader’s system: riso by day, riso-night in dark mode."
    },
    {
      "name": "paper",
      "description": "Light publication: cream stock with fibre, cobalt ink, Newsreader display serif."
    },
    {
      "name": "graphite",
      "description": "Retired brand dark (kept for existing sheets): charcoal felt with an ember glow, cream type."
    },
    {
      "name": "ink",
      "description": "High-contrast Swiss neutral: white stock, heavy black rules, Archivo."
    },
    {
      "name": "slate",
      "description": "Technical dark: blueprint grid, steel accent, IBM Plex Mono headings."
    },
    {
      "name": "none",
      "description": "No kit look: structure and behaviour with neutral tokens and system faces. The bare kit (tacksheet-base.css) carries only this theme, for one-off styles."
    }
  ],
  "elements": [
    {
      "name": "tk-page",
      "description": "Sheet column: 76% of the screen up to 110rem, never narrower than 43rem (set --tk-page-width on body to change it). Running text keeps a 46rem measure (--tk-measure); images, media, grids, tables and code use the full column. In the riso themes it sits on a translucent reading panel over the page texture; set --tk-panel:transparent (or a colour) on body to change it.",
      "attributes": [
        {
          "name": "wide",
          "type": "boolean",
          "default": "absent",
          "description": "Wider column: 94% of the screen up to 140rem (--tk-page-wide); running text keeps its measure."
        },
        {
          "name": "unnumbered",
          "type": "boolean",
          "default": "absent",
          "description": "Turn off the 01, 02… numbers on section headings."
        }
      ]
    },
    {
      "name": "tk-hero",
      "description": "Sheet header block (h1, dek, byline).",
      "attributes": []
    },
    {
      "name": "tk-section",
      "description": "Numbered section; h2 gets a deep-link anchor when the section has an id.",
      "attributes": [
        {
          "name": "id",
          "type": "string",
          "default": "—",
          "description": "Deep-link target, also used by tk-toc."
        },
        {
          "name": "unnumbered",
          "type": "boolean",
          "default": "absent",
          "description": "No number on this section’s heading."
        }
      ]
    },
    {
      "name": "tk-grid",
      "description": "Responsive grid; collapses to one column under 640px.",
      "attributes": [
        {
          "name": "cols",
          "type": "number",
          "default": "2",
          "description": "Columns on desktop (1–6)."
        }
      ]
    },
    {
      "name": "tk-split",
      "description": "Two-column split for genuinely parallel content.",
      "attributes": []
    },
    {
      "name": "tk-card",
      "description": "Raised card on the theme surface.",
      "attributes": []
    },
    {
      "name": "tk-callout",
      "description": "Callout with a rule in the kind colour and a rubber-stamped kind label.",
      "attributes": [
        {
          "name": "kind",
          "type": "enum note|tip|warning|decision|risk",
          "default": "note",
          "description": "Callout kind; also settable as data-kind."
        }
      ]
    },
    {
      "name": "tk-stat",
      "description": "Headline number (.tk-value) with a label (.tk-label) and delta (.tk-delta); a leading +/− colours the delta. A sparkline tk-chart inside sits under the number. A long value wraps and shrinks with its card instead of overflowing it.",
      "attributes": [
        {
          "name": "good",
          "type": "enum up|down",
          "default": "up",
          "description": "Which direction is good: good=\"down\" for latency, churn or cost."
        }
      ]
    },
    {
      "name": "tk-stats",
      "description": "Responsive row of tk-stat cards.",
      "attributes": []
    },
    {
      "name": "tk-figure",
      "description": "Figure wrapper with caption styling; its image opens the full-screen image viewer (previous and next across every viewable image on the page, a counter, the caption, swipe, pinch and double-tap zoom, Escape and arrow keys) with the figcaption.",
      "attributes": []
    },
    {
      "name": "tk-table",
      "description": "Sortable, filterable table from JSON or an HTML table; sticky header and 44px sort buttons. Formatted figures (1,284, $12.9k, −5, 42%) sort as numbers. A shade marks each side a wide table can still scroll to.",
      "attributes": [
        {
          "name": "data",
          "type": "json-ref",
          "default": "—",
          "description": "JSON script id (\"#sales\") or inline <script type=\"application/json\"> child. Accepts {columns, rows} or an array of objects."
        },
        {
          "name": "filter",
          "type": "string",
          "default": "—",
          "description": "Name of a state value holding filter text (for example bound to tk-search). Every word of it, and of the filter box, must appear somewhere in a row. Re-applied when state changes. Not an expression."
        },
        {
          "name": "sortable",
          "type": "boolean",
          "default": "absent",
          "description": "Click a column header to sort."
        },
        {
          "name": "searchable",
          "type": "boolean",
          "default": "absent",
          "description": "Show a filter box (alias: filterable)."
        },
        {
          "name": "filterable",
          "type": "boolean",
          "default": "absent",
          "description": "Alias of searchable."
        },
        {
          "name": "stack",
          "type": "boolean",
          "default": "absent",
          "description": "On screens 640px wide or less, show each row as a card with every value under its column name, instead of a table to scroll sideways."
        }
      ]
    },
    {
      "name": "tk-timeline",
      "description": "Vertical timeline; from JSON {items:[{when,text}]} or a plain list.",
      "attributes": [
        {
          "name": "data",
          "type": "json-ref",
          "default": "—",
          "description": "JSON script id (\"#sales\") or inline <script type=\"application/json\"> child."
        }
      ]
    },
    {
      "name": "tk-steps",
      "description": "Numbered step list.",
      "attributes": []
    },
    {
      "name": "tk-kbd",
      "description": "Keyboard-key chip.",
      "attributes": []
    },
    {
      "name": "tk-code",
      "description": "Code block with highlighting (js, python, json, html, css, bash) and a copy button.",
      "attributes": [
        {
          "name": "language",
          "type": "string",
          "default": "text",
          "description": "Highlight language."
        }
      ]
    },
    {
      "name": "tk-details",
      "description": "Styled disclosure wrapper.",
      "attributes": [
        {
          "name": "summary",
          "type": "string",
          "default": "Details",
          "description": "Summary label."
        }
      ]
    },
    {
      "name": "tk-page-section",
      "description": "One page inside tk-pager.",
      "attributes": []
    },
    {
      "name": "tk-toc",
      "description": "Table of contents from h2 plus h3s carrying an author id; sticky on desktop, collapsible on phone. Headings inside cards, callouts, tabs, accordions, slides, pages, details, hidden content or a [data-tk-toc=\"skip\"] block stay out.",
      "attributes": [
        {
          "name": "sticky",
          "type": "boolean",
          "default": "absent",
          "description": "Stick while scrolling on wide screens."
        },
        {
          "name": "collapsible",
          "type": "boolean",
          "default": "absent",
          "description": "Collapse behind a toggle on phone widths."
        },
        {
          "name": "title",
          "type": "string",
          "default": "On this sheet",
          "description": "Heading label."
        }
      ]
    },
    {
      "name": "tk-tabs",
      "description": "Tabbed panels; children carry label=\"…\". Arrow-key navigation.",
      "attributes": []
    },
    {
      "name": "tk-accordion",
      "description": "Stacked disclosures; children carry label=\"…\". Panels open and close independently.",
      "attributes": []
    },
    {
      "name": "tk-pager",
      "description": "Multi-page sheet; shows one tk-page-section at a time with prev/next and arrow keys.",
      "attributes": []
    },
    {
      "name": "tk-progress",
      "description": "Sticky reading-progress bar.",
      "attributes": []
    },
    {
      "name": "tk-gallery",
      "description": "Image set; click any image (or Tab to it and press Enter) for the full-screen image viewer (previous and next across every viewable image on the page, a counter, the caption, swipe, pinch and double-tap zoom, Escape and arrow keys). A data-caption on a nested img becomes its caption.",
      "attributes": [
        {
          "name": "layout",
          "type": "enum grid|masonry|carousel",
          "default": "grid",
          "description": "Gallery arrangement."
        }
      ]
    },
    {
      "name": "tk-lightbox",
      "description": "Marks a set of images for the full-screen image viewer (previous and next across every viewable image on the page, a counter, the caption, swipe, pinch and double-tap zoom, Escape and arrow keys); every content image on the page opens it from 1.5.0, so tk-lightbox only guarantees that its images do (even without alt text). Focus returns to the image on close.",
      "attributes": []
    },
    {
      "name": "tk-compare",
      "description": "Before/after slider over two images (before on the left); drag anywhere or use the arrow keys.",
      "attributes": [
        {
          "name": "before-label",
          "type": "string",
          "default": "Before",
          "description": "Tag on the before side."
        },
        {
          "name": "after-label",
          "type": "string",
          "default": "After",
          "description": "Tag on the after side."
        },
        {
          "name": "before",
          "type": "url",
          "default": "first img",
          "description": "Before image URL."
        },
        {
          "name": "after",
          "type": "url",
          "default": "second img",
          "description": "After image URL."
        },
        {
          "name": "before-alt",
          "type": "string",
          "default": "Before",
          "description": "Alt text for the image built from before=."
        },
        {
          "name": "after-alt",
          "type": "string",
          "default": "After",
          "description": "Alt text for the image built from after=."
        }
      ]
    },
    {
      "name": "tk-zoom",
      "description": "Pan/zoom viewer for large images: buttons, pinch or Ctrl/Cmd+wheel zoom around the pointer, press-drag-release pans a zoomed image; a plain wheel scrolls the page; arrow keys, + and - on the focused image.",
      "attributes": []
    },
    {
      "name": "tk-annotate",
      "description": "Image with numbered hotspot pins and a notes list.",
      "attributes": [
        {
          "name": "data",
          "type": "json-ref",
          "default": "—",
          "description": "Hotspots: [{x: 0–100, y: 0–100, label, note}]."
        }
      ]
    },
    {
      "name": "tk-image",
      "description": "Responsive captioned image around a nested <img> (give it alt text); a same-review ./files/… src loads through the shell file.fetch bridge when embedded. Opens the full-screen image viewer (previous and next across every viewable image on the page, a counter, the caption, swipe, pinch and double-tap zoom, Escape and arrow keys) with its caption.",
      "attributes": [
        {
          "name": "caption",
          "type": "string",
          "default": "—",
          "description": "Caption text."
        },
        {
          "name": "credit",
          "type": "string",
          "default": "—",
          "description": "Credit line appended to the caption."
        }
      ]
    },
    {
      "name": "tk-video",
      "description": "Video with chapters, playback-rate control and timestamp links.",
      "attributes": [
        {
          "name": "src",
          "type": "url",
          "default": "—",
          "description": "Video URL (or nest a <video> with <track> captions); same-review ./files/… names load through the shell file.fetch bridge when embedded."
        },
        {
          "name": "poster",
          "type": "url",
          "default": "—",
          "description": "Poster image (same bridge as src)."
        },
        {
          "name": "chapters",
          "type": "json-ref",
          "default": "—",
          "description": "Chapters: [{t: seconds, label}]."
        },
        {
          "name": "rates",
          "type": "string",
          "default": "—",
          "description": "Comma-separated playback rates, e.g. \"0.75,1,1.5,2\"."
        },
        {
          "name": "rate",
          "type": "number",
          "default": "1",
          "description": "Initial playback rate."
        },
        {
          "name": "data-seek",
          "type": "time",
          "default": "—",
          "description": "Goes on <a> links inside the tk-video, not on the element itself: data-seek=\"65\" or \"1:05\" seeks there and plays when the link is clicked."
        }
      ]
    },
    {
      "name": "tk-audio",
      "description": "Designed audio player (play, seek, time) with optional chapters.",
      "attributes": [
        {
          "name": "title",
          "type": "string",
          "default": "Audio player",
          "description": "Accessible name of the player."
        },
        {
          "name": "src",
          "type": "url",
          "default": "—",
          "description": "Audio URL (or nest an <audio>); same-review ./files/… names load through the shell file.fetch bridge when embedded."
        },
        {
          "name": "chapters",
          "type": "json-ref",
          "default": "—",
          "description": "Chapters: [{t: seconds, label}]."
        }
      ]
    },
    {
      "name": "tk-slides",
      "description": "Slide deck; direct children are slides. Arrow keys supported.",
      "attributes": []
    },
    {
      "name": "tk-scrolly",
      "description": "Scroll-driven narrative: .tk-step children activate in turn; bound charts update from per-step JSON.",
      "attributes": [
        {
          "name": "for",
          "type": "id-ref",
          "default": "—",
          "description": "Bound tk-chart id updated from each step's inline JSON."
        }
      ]
    },
    {
      "name": "tk-chart",
      "description": "ECharts chart with theme palette, responsive resize and a data-table fallback.",
      "attributes": [
        {
          "name": "type",
          "type": "enum",
          "default": "bar",
          "description": "Chart type; see the manifest chartTypes list for all 40+ types."
        },
        {
          "name": "data",
          "type": "json-ref",
          "default": "—",
          "description": "JSON script id (\"#sales\") or inline <script type=\"application/json\"> child."
        },
        {
          "name": "x",
          "type": "string",
          "default": "—",
          "description": "Label column (rows data) for cartesian charts."
        },
        {
          "name": "y",
          "type": "string",
          "default": "—",
          "description": "Value column(s), comma-separated (rows data)."
        },
        {
          "name": "series",
          "type": "string",
          "default": "—",
          "description": "Grouping column for long-form rows data."
        },
        {
          "name": "filter",
          "type": "expression",
          "default": "—",
          "description": "Row predicate; re-evaluated when state changes."
        },
        {
          "name": "x-domain",
          "type": "string",
          "default": "—",
          "description": "\"min,max\" clamp for a value x-axis."
        },
        {
          "name": "y-domain",
          "type": "string",
          "default": "—",
          "description": "\"min,max\" clamp for a value y-axis."
        },
        {
          "name": "facet",
          "type": "string",
          "default": "—",
          "description": "Column for small multiples: one chart per value."
        },
        {
          "name": "options",
          "type": "json-ref",
          "default": "—",
          "description": "Raw ECharts option deep-merged over the generated one (escape hatch)."
        },
        {
          "name": "title",
          "type": "string",
          "default": "—",
          "description": "Accessible title, also the aria-label."
        },
        {
          "name": "subtitle",
          "type": "string",
          "default": "—",
          "description": "Subtitle line."
        },
        {
          "name": "size",
          "type": "enum small|large",
          "default": "—",
          "description": "Chart height preset."
        },
        {
          "name": "frame",
          "type": "enum full",
          "default": "—",
          "description": "Keep categories and the value axis of the unfiltered data, so filters reveal data in a stable frame (tk-scrolly sets it on its chart)."
        },
        {
          "name": "labels",
          "type": "enum none",
          "default": "—",
          "description": "labels=\"none\" turns off the direct end labels on 2–4 line series."
        },
        {
          "name": "z",
          "type": "string",
          "default": "—",
          "description": "Bubble size column for rows data (alias: r; plain size is deprecated because size means the height preset)."
        },
        {
          "name": "r",
          "type": "string",
          "default": "—",
          "description": "Alias of z."
        },
        {
          "name": "map",
          "type": "enum usa|world",
          "default": "usa",
          "description": "Choropleth / dot-map geography."
        },
        {
          "name": "smooth",
          "type": "boolean",
          "default": "absent",
          "description": "Line, area, stacked-area: smooth the curves (smooth=\"false\" keeps them straight)."
        },
        {
          "name": "step",
          "type": "enum start|middle|end",
          "default": "—",
          "description": "Line, area, stacked-area: draw a step line."
        },
        {
          "name": "bins",
          "type": "number",
          "default": "12 (contour 24)",
          "description": "Histogram bin count (3–40); contour/density grid size (8–48)."
        },
        {
          "name": "value",
          "type": "string",
          "default": "—",
          "description": "Box plot on rows data: value column when y is not set."
        },
        {
          "name": "year",
          "type": "number",
          "default": "current year",
          "description": "Calendar heatmap: year to draw when the data has no dates."
        },
        {
          "name": "layout",
          "type": "enum force|circular|none",
          "default": "force",
          "description": "Network/graph layout."
        },
        {
          "name": "open",
          "type": "string",
          "default": "open",
          "description": "Candlestick on rows data: open column (x defaults to date)."
        },
        {
          "name": "close",
          "type": "string",
          "default": "close",
          "description": "Candlestick on rows data: close column."
        },
        {
          "name": "low",
          "type": "string",
          "default": "low",
          "description": "Candlestick on rows data: low column."
        },
        {
          "name": "high",
          "type": "string",
          "default": "high",
          "description": "Candlestick on rows data: high column."
        },
        {
          "name": "a",
          "type": "string",
          "default": "a",
          "description": "Dumbbell on rows data: first value column (x is the label column, default label)."
        },
        {
          "name": "b",
          "type": "string",
          "default": "b",
          "description": "Dumbbell on rows data: second value column."
        },
        {
          "name": "a-label",
          "type": "string",
          "default": "Before",
          "description": "Dumbbell legend name for a."
        },
        {
          "name": "b-label",
          "type": "string",
          "default": "After",
          "description": "Dumbbell legend name for b."
        },
        {
          "name": "error",
          "type": "string",
          "default": "error",
          "description": "Error bars on rows data: error column (x and y default to label and value)."
        },
        {
          "name": "kinds",
          "type": "string",
          "default": "bar,line",
          "description": "Combo: comma-separated bar|line per series."
        },
        {
          "name": "axes",
          "type": "string",
          "default": "0,1",
          "description": "Combo: comma-separated value-axis index (0 left, 1 right) per series."
        },
        {
          "name": "y-label",
          "type": "string",
          "default": "—",
          "description": "Combo: left axis name."
        },
        {
          "name": "y2-label",
          "type": "string",
          "default": "—",
          "description": "Combo: right axis name."
        }
      ]
    },
    {
      "name": "tk-state",
      "description": "Registers a named reactive value.",
      "attributes": [
        {
          "name": "name",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "value",
          "type": "json",
          "default": "—",
          "description": "Initial value (JSON literal) or an inline JSON child."
        }
      ]
    },
    {
      "name": "tk-slider",
      "description": "Range slider bound to state.",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "min",
          "type": "number",
          "default": "0",
          "description": "Minimum."
        },
        {
          "name": "max",
          "type": "number",
          "default": "100",
          "description": "Maximum."
        },
        {
          "name": "step",
          "type": "string",
          "default": "1",
          "description": "Step."
        },
        {
          "name": "value",
          "type": "json",
          "default": "min",
          "description": "Initial value."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Field label."
        }
      ]
    },
    {
      "name": "tk-range",
      "description": "Dual slider bound to a [lo, hi] state pair.",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "min",
          "type": "number",
          "default": "0",
          "description": "Minimum."
        },
        {
          "name": "max",
          "type": "number",
          "default": "100",
          "description": "Maximum."
        },
        {
          "name": "step",
          "type": "string",
          "default": "1",
          "description": "Step."
        },
        {
          "name": "value",
          "type": "json",
          "default": "[min, max]",
          "description": "Initial [lo, hi] pair (JSON) when the state has none yet."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Field label."
        }
      ]
    },
    {
      "name": "tk-select",
      "description": "Dropdown bound to state.",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "options",
          "type": "json-ref",
          "default": "—",
          "description": "Options: [{value,label}] or plain values."
        },
        {
          "name": "value",
          "type": "json",
          "default": "first option",
          "description": "Initial value (JSON literal) when the state has none yet."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Field label."
        }
      ]
    },
    {
      "name": "tk-toggle",
      "description": "Switch bound to a boolean.",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "value",
          "type": "json",
          "default": "false",
          "description": "Initial value (JSON literal) when the state has none yet."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Visible label."
        }
      ]
    },
    {
      "name": "tk-radio-group",
      "description": "Radio group bound to state.",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "options",
          "type": "json-ref",
          "default": "—",
          "description": "Options: [{value,label}]."
        },
        {
          "name": "value",
          "type": "json",
          "default": "first option",
          "description": "Initial value (JSON literal) when the state has none yet."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Group legend."
        }
      ]
    },
    {
      "name": "tk-search",
      "description": "Debounced search box bound to state.",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "placeholder",
          "type": "string",
          "default": "Search…",
          "description": "Placeholder."
        },
        {
          "name": "value",
          "type": "json",
          "default": "\"\"",
          "description": "Initial value (JSON literal) when the state has none yet."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Field label."
        }
      ]
    },
    {
      "name": "tk-number",
      "description": "Number input bound to state.",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "min",
          "type": "number",
          "default": "—",
          "description": "Minimum."
        },
        {
          "name": "max",
          "type": "number",
          "default": "—",
          "description": "Maximum."
        },
        {
          "name": "step",
          "type": "number",
          "default": "—",
          "description": "Step."
        },
        {
          "name": "value",
          "type": "json",
          "default": "0",
          "description": "Initial value (JSON literal) when the state has none yet."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Field label."
        }
      ]
    },
    {
      "name": "tk-date",
      "description": "Date input bound to state (ISO yyyy-mm-dd).",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "min",
          "type": "string",
          "default": "—",
          "description": "Earliest date."
        },
        {
          "name": "max",
          "type": "string",
          "default": "—",
          "description": "Latest date."
        },
        {
          "name": "value",
          "type": "json",
          "default": "\"\"",
          "description": "Initial date as a JSON string (value='\"2026-10-01\"') when the state has none yet."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Field label."
        }
      ]
    },
    {
      "name": "tk-play",
      "description": "Animates a numeric state from→to with a scrubber. It follows the shared state: Play starts from the current value (from `from` once a run has finished), another control moving the value stops the run, and with a step only whole steps are written. Honors reduced motion.",
      "attributes": [
        {
          "name": "bind",
          "type": "string",
          "default": "required",
          "description": "Reactive state name written with Tacksheet.state.set / tk-state."
        },
        {
          "name": "from",
          "type": "number",
          "default": "0",
          "description": "Start value."
        },
        {
          "name": "to",
          "type": "number",
          "default": "100",
          "description": "End value."
        },
        {
          "name": "step",
          "type": "number | \"any\"",
          "default": "1 when from and to are whole numbers, else any",
          "description": "Values are written in whole steps (each one once); \"any\" animates smoothly."
        },
        {
          "name": "duration",
          "type": "number",
          "default": "4000",
          "description": "Milliseconds for a run from `from` to `to` (200 or more)."
        },
        {
          "name": "loop",
          "type": "boolean",
          "default": "absent",
          "description": "Loop until paused; the end value shows for one step before the run starts over."
        },
        {
          "name": "value",
          "type": "json",
          "default": "from",
          "description": "Initial value (JSON literal) when the state has none yet."
        },
        {
          "name": "data-motion-ok",
          "type": "boolean",
          "default": "absent",
          "description": "Animate even when the reader prefers reduced motion (otherwise Play jumps to the end value)."
        },
        {
          "name": "label",
          "type": "string",
          "default": "bind name",
          "description": "Field label."
        }
      ]
    },
    {
      "name": "tk-text",
      "description": "Live template: {name}, {expr}, {value:.2f}, {rate:%}.",
      "attributes": [
        {
          "name": "template",
          "type": "string",
          "default": "text content",
          "description": "Template using state expressions."
        }
      ]
    },
    {
      "name": "tk-show",
      "description": "Shows its content only while the expression is truthy.",
      "attributes": [
        {
          "name": "when",
          "type": "expression",
          "default": "true",
          "description": "Safe expression over state (==, !=, <, >, &&, ||, !, ternary, min/max/abs/round…)."
        }
      ]
    },
    {
      "name": "tk-compute",
      "description": "Derived value: re-evaluates expr on every change and writes name.",
      "attributes": [
        {
          "name": "name",
          "type": "string",
          "default": "required",
          "description": "Derived value name."
        },
        {
          "name": "expr",
          "type": "expression",
          "default": "required",
          "description": "Safe expression over state."
        },
        {
          "name": "value",
          "type": "expression",
          "default": "—",
          "description": "Alias of expr."
        }
      ]
    },
    {
      "name": "tk-feedback",
      "description": "Free-text reply through the shell bridge; disabled outside Tacksheet. From 1.6.0 a reader’s unsent answers are kept in their own browser through the shell (feedback.draft.*): a reload brings them back (\"Restored your unsent answers · Start over\"), a saved reply drops them, and after a saved reply the button waits for Send another. Give the widget an id so its answers follow it when it moves on the page. From 1.7.0 its title is sent with the reply, so the reply says what it answers. From 1.7.0, form=\"<tk-form id>\" joins it to that tk-form: it answers right here with no Send button of its own, shows whether it is answered, and its answer is sent as the field name= (default its id) in the form’s one reply.",
      "attributes": [
        {
          "name": "title",
          "type": "string",
          "default": "Leave feedback",
          "description": "Widget heading."
        },
        {
          "name": "prompt",
          "type": "string",
          "default": "Your reply",
          "description": "Textarea label."
        },
        {
          "name": "maxlength",
          "type": "number",
          "default": "—",
          "description": "Longest reply in characters."
        },
        {
          "name": "submit-label",
          "type": "string",
          "default": "Send feedback",
          "description": "Send button label."
        },
        {
          "name": "expected-revision",
          "type": "number",
          "default": "shell value",
          "description": "Optimistic-concurrency revision sent with the submission."
        },
        {
          "name": "form",
          "type": "id-ref",
          "default": "—",
          "description": "From 1.7.0: the id of a tk-form on the sheet. The widget answers in place without its own Send button, and its answer becomes a field of that form’s one reply. Without it the widget sends its own reply as before."
        },
        {
          "name": "name",
          "type": "string",
          "default": "id",
          "description": "From 1.7.0, with form: the field name of the answer in the form’s reply (a letter, then letters, digits, - or _; at most 64). Defaults to the widget’s id."
        },
        {
          "name": "required",
          "type": "boolean",
          "default": "absent",
          "description": "From 1.7.0, with form: the form will not send until this is answered; Send takes the reader to it."
        }
      ]
    },
    {
      "name": "tk-question",
      "description": "Single reader question through the bridge. From 1.6.0 a reader’s unsent answers are kept in their own browser through the shell (feedback.draft.*): a reload brings them back (\"Restored your unsent answers · Start over\"), a saved reply drops them, and after a saved reply the button waits for Send another. Give the widget an id so its answers follow it when it moves on the page. From 1.7.0, form=\"<tk-form id>\" joins it to that tk-form: it answers right here with no Send button of its own, shows whether it is answered, and its answer is sent as the field name= (default its id) in the form’s one reply.",
      "attributes": [
        {
          "name": "min-label",
          "type": "string",
          "default": "—",
          "description": "Scale: words under the lowest value."
        },
        {
          "name": "max-label",
          "type": "string",
          "default": "—",
          "description": "Scale: words under the highest value."
        },
        {
          "name": "type",
          "type": "enum choice|scale|rating|yes-no|text",
          "default": "text",
          "description": "Answer widget."
        },
        {
          "name": "question",
          "type": "string",
          "default": "Question",
          "description": "Question text."
        },
        {
          "name": "options",
          "type": "json-ref",
          "default": "—",
          "description": "Choice options: [{value,label}]."
        },
        {
          "name": "min",
          "type": "number",
          "default": "1",
          "description": "Scale minimum."
        },
        {
          "name": "max",
          "type": "number",
          "default": "5",
          "description": "Scale maximum."
        },
        {
          "name": "expected-revision",
          "type": "number",
          "default": "shell value",
          "description": "Revision sent with the submission."
        },
        {
          "name": "form",
          "type": "id-ref",
          "default": "—",
          "description": "From 1.7.0: the id of a tk-form on the sheet. The widget answers in place without its own Send button, and its answer becomes a field of that form’s one reply. Without it the widget sends its own reply as before."
        },
        {
          "name": "name",
          "type": "string",
          "default": "id",
          "description": "From 1.7.0, with form: the field name of the answer in the form’s reply (a letter, then letters, digits, - or _; at most 64). Defaults to the widget’s id."
        },
        {
          "name": "required",
          "type": "boolean",
          "default": "absent",
          "description": "From 1.7.0, with form: the form will not send until this is answered; Send takes the reader to it."
        }
      ]
    },
    {
      "name": "tk-form",
      "description": "Multi-field reply rendered from the server form schema (identical shape). From 1.6.0 a reader’s unsent answers are kept in their own browser through the shell (feedback.draft.*): a reload brings them back (\"Restored your unsent answers · Start over\"), a saved reply drops them, and after a saved reply the button waits for Send another. Give the widget an id so its answers follow it when it moves on the page. From 1.7.0 reply widgets anywhere on the sheet join it with form=\"<its id>\": it then shows how many are answered (\"3 of 5 answered\"), lists the ones still open (each one jumps there), marks a missing required answer where it sits and sends everything, its own fields included, as one reply through one Send dialog. schema may be left out when the joined widgets are its only questions; tackit.py sheet schema derives the matching --form schema.",
      "attributes": [
        {
          "name": "schema",
          "type": "json-ref",
          "default": "required (optional from 1.7.0 when widgets join the form)",
          "description": "Form schema: {fields:[…]} or JSON-schema {properties}."
        },
        {
          "name": "title",
          "type": "string",
          "default": "schema title",
          "description": "Widget heading."
        },
        {
          "name": "form-id",
          "type": "string",
          "default": "schema id",
          "description": "Echoed in the payload."
        },
        {
          "name": "submit-label",
          "type": "string",
          "default": "Submit",
          "description": "Send button label."
        },
        {
          "name": "expected-revision",
          "type": "number",
          "default": "shell value",
          "description": "Revision sent with the submission."
        },
        {
          "name": "heading",
          "type": "enum shown|hidden",
          "default": "shown",
          "description": "From 1.7.0: hidden keeps the heading for screen readers only, for a short closing Send block."
        }
      ]
    },
    {
      "name": "tk-vote",
      "description": "Choose between options/variants through the bridge. From 1.6.0 a reader’s unsent answers are kept in their own browser through the shell (feedback.draft.*): a reload brings them back (\"Restored your unsent answers · Start over\"), a saved reply drops them, and after a saved reply the button waits for Send another. Give the widget an id so its answers follow it when it moves on the page. From 1.7.0, form=\"<tk-form id>\" joins it to that tk-form: it answers right here with no Send button of its own, shows whether it is answered, and its answer is sent as the field name= (default its id) in the form’s one reply.",
      "attributes": [
        {
          "name": "question",
          "type": "string",
          "default": "Vote",
          "description": "Vote question."
        },
        {
          "name": "options",
          "type": "json-ref",
          "default": "required",
          "description": "Options: [{value,label}]."
        },
        {
          "name": "expected-revision",
          "type": "number",
          "default": "shell value",
          "description": "Revision sent with the submission."
        },
        {
          "name": "form",
          "type": "id-ref",
          "default": "—",
          "description": "From 1.7.0: the id of a tk-form on the sheet. The widget answers in place without its own Send button, and its answer becomes a field of that form’s one reply. Without it the widget sends its own reply as before."
        },
        {
          "name": "name",
          "type": "string",
          "default": "id",
          "description": "From 1.7.0, with form: the field name of the answer in the form’s reply (a letter, then letters, digits, - or _; at most 64). Defaults to the widget’s id."
        },
        {
          "name": "required",
          "type": "boolean",
          "default": "absent",
          "description": "From 1.7.0, with form: the form will not send until this is answered; Send takes the reader to it."
        }
      ]
    },
    {
      "name": "tk-request-info",
      "description": "Reader asks for more info on a section. From 1.6.0 a reader’s unsent answers are kept in their own browser through the shell (feedback.draft.*): a reload brings them back (\"Restored your unsent answers · Start over\"), a saved reply drops them, and after a saved reply the button waits for Send another. Give the widget an id so its answers follow it when it moves on the page. From 1.7.0, form=\"<tk-form id>\" joins it to that tk-form: it answers right here with no Send button of its own, shows whether it is answered, and its answer is sent as the field name= (default its id) in the form’s one reply.",
      "attributes": [
        {
          "name": "section",
          "type": "string",
          "default": "title, else \"this section\"",
          "description": "Section name or id included in the payload and the prompt."
        },
        {
          "name": "expected-revision",
          "type": "number",
          "default": "shell value",
          "description": "Revision sent with the submission."
        },
        {
          "name": "form",
          "type": "id-ref",
          "default": "—",
          "description": "From 1.7.0: the id of a tk-form on the sheet. The widget answers in place without its own Send button, and its answer becomes a field of that form’s one reply. Without it the widget sends its own reply as before."
        },
        {
          "name": "name",
          "type": "string",
          "default": "id",
          "description": "From 1.7.0, with form: the field name of the answer in the form’s reply (a letter, then letters, digits, - or _; at most 64). Defaults to the widget’s id."
        },
        {
          "name": "required",
          "type": "boolean",
          "default": "absent",
          "description": "From 1.7.0, with form: the form will not send until this is answered; Send takes the reader to it."
        }
      ]
    },
    {
      "name": "tk-annotate-feedback",
      "description": "Reader pins a comment on an image. From 1.6.0 a reader’s unsent answers are kept in their own browser through the shell (feedback.draft.*): a reload brings them back (\"Restored your unsent answers · Start over\"), a saved reply drops them, and after a saved reply the button waits for Send another. Give the widget an id so its answers follow it when it moves on the page. From 1.7.0 its title is sent with the reply; joined to a form, the pin (x and y, 0–100) is kept with the comment. From 1.7.0, form=\"<tk-form id>\" joins it to that tk-form: it answers right here with no Send button of its own, shows whether it is answered, and its answer is sent as the field name= (default its id) in the form’s one reply.",
      "attributes": [
        {
          "name": "src",
          "type": "url",
          "default": "required",
          "description": "Image URL."
        },
        {
          "name": "alt",
          "type": "string",
          "default": "Image to comment on",
          "description": "Alt text."
        },
        {
          "name": "expected-revision",
          "type": "number",
          "default": "shell value",
          "description": "Revision sent with the submission."
        },
        {
          "name": "form",
          "type": "id-ref",
          "default": "—",
          "description": "From 1.7.0: the id of a tk-form on the sheet. The widget answers in place without its own Send button, and its answer becomes a field of that form’s one reply. Without it the widget sends its own reply as before."
        },
        {
          "name": "name",
          "type": "string",
          "default": "id",
          "description": "From 1.7.0, with form: the field name of the answer in the form’s reply (a letter, then letters, digits, - or _; at most 64). Defaults to the widget’s id."
        },
        {
          "name": "required",
          "type": "boolean",
          "default": "absent",
          "description": "From 1.7.0, with form: the form will not send until this is answered; Send takes the reader to it."
        }
      ]
    },
    {
      "name": "tk-model",
      "description": "GLB viewer: orbit, authored path, fly and walk modes; poster, hotspots, WebGL fallback.",
      "attributes": [
        {
          "name": "title",
          "type": "string",
          "default": "3D model",
          "description": "Accessible name of the viewer (aria-label also works), shown as its caption."
        },
        {
          "name": "src",
          "type": "url",
          "default": "—",
          "description": "Same-review ./files/… file, loaded through the shell file.fetch bridge when embedded (or fetched directly when standalone)."
        },
        {
          "name": "glb",
          "type": "base64 | id-ref",
          "default": "—",
          "description": "Inline base64 GLB, or \"#id\" of an element holding the base64 (embed once, reuse in several viewers); renders with zero network under connect-src 'none'. Prefer for models under ~1 MB."
        },
        {
          "name": "mode",
          "type": "enum orbit|path|fly|walk",
          "default": "orbit",
          "description": "Navigation mode."
        },
        {
          "name": "poster",
          "type": "url",
          "default": "—",
          "description": "Poster shown until the viewer starts."
        },
        {
          "name": "background",
          "type": "color",
          "default": "#101014",
          "description": "Scene background."
        },
        {
          "name": "auto-rotate",
          "type": "boolean",
          "default": "absent",
          "description": "Slow turntable (paused under reduced motion)."
        },
        {
          "name": "speed",
          "type": "number",
          "default": "1",
          "description": "Rotation/movement speed multiplier."
        },
        {
          "name": "data",
          "type": "json-ref",
          "default": "—",
          "description": "Hotspots [{label,pos,view}] and path keyframes [{label,pos,target}]; for fly/walk also terrain {…}, place {x,z,scale}, and fly/walk start {pos,target}."
        }
      ]
    },
    {
      "name": "tk-scene",
      "description": "Data-driven 3D scene from JSON: points, lines, meshes, point clouds, terrain heightmaps (smoothed, elevation ramp, contours). Unset colours follow the theme.",
      "attributes": [
        {
          "name": "data",
          "type": "json-ref",
          "default": "required",
          "description": "Scene JSON; see docs/sheet-kit.md."
        },
        {
          "name": "title",
          "type": "string",
          "default": "3D scene",
          "description": "Accessible name of the scene (aria-label also works), shown as its caption."
        }
      ]
    },
    {
      "name": "tk-tape-title",
      "description": "Washi-tape title: a real h1, h2 or h3 printed on a translucent strip of tape with torn ends, fibre and a faint pattern; each line gets its own strip. The ink is checked against the tape as it lands on the page (4.5:1, or 3:1 for 24px+ text) and replaced, with a console warning, when it fails. A title over 34 characters prints smaller, and over 60 smaller still, most of all on phones; keep a level-1 title to about six words.",
      "attributes": [
        {
          "name": "level",
          "type": "enum 1|2|3",
          "default": "1",
          "description": "Heading level built when the title is plain text; a nested <h1>, <h2> or <h3> is used as it is."
        },
        {
          "name": "tape",
          "type": "enum kraft|frost|vermilion|cobalt|plum|sunflower|cornflower|blush",
          "default": "kraft",
          "description": "Tape colour from the riso tape palette."
        },
        {
          "name": "pattern",
          "type": "enum plain|grid|dots|stripes|kraft",
          "default": "plain",
          "description": "Faint pattern printed on the tape."
        },
        {
          "name": "ink",
          "type": "enum auto|cobalt|black|cream|vermilion|plum",
          "default": "auto",
          "description": "Title ink. auto picks cobalt, near-black or cream for the tape; a named ink that fails contrast is overridden."
        },
        {
          "name": "tilt",
          "type": "number",
          "default": "-1.2",
          "description": "Rotation of the first strip in degrees (-6 to 6); later lines alternate around it."
        },
        {
          "name": "hand",
          "type": "boolean",
          "default": "absent",
          "description": "Letter the title by hand (Kalam marker) instead of the heading face."
        }
      ]
    },
    {
      "name": "tk-taped",
      "description": "Tapes its content to the sheet: an image becomes a print with a cream border, anything else a pinned piece of paper; strips of tape hold it at the top or the corners.",
      "attributes": [
        {
          "name": "tape",
          "type": "enum kraft|frost|vermilion|cobalt|plum|sunflower|cornflower|blush",
          "default": "kraft",
          "description": "Tape colour."
        },
        {
          "name": "pattern",
          "type": "enum plain|grid|dots|stripes|kraft",
          "default": "plain",
          "description": "Faint tape pattern."
        },
        {
          "name": "at",
          "type": "enum top|corners|diagonal",
          "default": "top",
          "description": "Where the strips go: one across the top, two top corners, or top-left and bottom-right."
        },
        {
          "name": "tilt",
          "type": "number",
          "default": "1",
          "description": "Rotation of the print in degrees (-6 to 6)."
        },
        {
          "name": "frame",
          "type": "enum print|none",
          "default": "print",
          "description": "print adds a cream photo border; none tapes the content as it is."
        },
        {
          "name": "caption",
          "type": "string",
          "default": "—",
          "description": "Handwritten caption on the print’s lower border."
        }
      ]
    },
    {
      "name": "tk-stamp",
      "description": "Rubber stamp in one riso ink, worn by the grain of the pad: APPROVED, DRAFT or your own word, with an optional date line. A long word list wraps inside the column; keep stamps to a word or two and three or fewer on a sheet.",
      "attributes": [
        {
          "name": "kind",
          "type": "enum approved|draft|rejected|final|shared|seen",
          "default": "—",
          "description": "Preset word and ink; text inside the element replaces the word."
        },
        {
          "name": "ink",
          "type": "enum cobalt|vermilion|plum",
          "default": "by kind (cobalt)",
          "description": "Stamp ink."
        },
        {
          "name": "shape",
          "type": "enum box|double|round",
          "default": "box",
          "description": "Stamp outline."
        },
        {
          "name": "date",
          "type": "string",
          "default": "—",
          "description": "Second, smaller line such as a date."
        },
        {
          "name": "tilt",
          "type": "number",
          "default": "-6",
          "description": "Rotation in degrees (-20 to 20)."
        }
      ]
    },
    {
      "name": "tk-slip",
      "description": "Torn, lined slip of cream paper with a handwritten note, held by a strip of tape. Use it for margin notes and asides; it is never a yellow square sticky note.",
      "attributes": [
        {
          "name": "tape",
          "type": "enum kraft|frost|vermilion|cobalt|plum|sunflower|cornflower|blush|none",
          "default": "kraft",
          "description": "Tape strip holding the slip (none for no tape)."
        },
        {
          "name": "from",
          "type": "string",
          "default": "—",
          "description": "Signature line, such as a name and a date."
        },
        {
          "name": "float",
          "type": "enum left|right",
          "default": "—",
          "description": "On wide sheets the slip floats beside the text; phones keep it in the flow."
        },
        {
          "name": "tilt",
          "type": "number",
          "default": "-1.2",
          "description": "Rotation in degrees (-6 to 6)."
        }
      ]
    },
    {
      "name": "tk-mark",
      "description": "Inline emphasis in riso ink: a highlighter swash, a hand-drawn underline or a ring around a word. Text keeps its own colour and contrast.",
      "attributes": [
        {
          "name": "kind",
          "type": "enum highlight|underline|circle",
          "default": "highlight",
          "description": "Mark style."
        },
        {
          "name": "ink",
          "type": "enum sunflower|vermilion|cobalt|plum|cornflower",
          "default": "sunflower (underline and circle: vermilion)",
          "description": "Mark ink."
        }
      ]
    },
    {
      "name": "tk-torn",
      "description": "Torn-paper divider: a ragged strip of paper between parts of a sheet, with an optional printed label.",
      "attributes": [
        {
          "name": "paper",
          "type": "enum kraft|cream|cobalt|vermilion|plum|sunflower",
          "default": "kraft",
          "description": "Paper of the torn strip; a label prints in an ink that passes on it."
        }
      ]
    },
    {
      "name": "tk-duotone",
      "description": "Riso duotone print of an image: two inks, slightly off register, overprinting where they meet, on paper that stays cream on night stock.",
      "attributes": [
        {
          "name": "inks",
          "type": "string",
          "default": "cobalt,vermilion",
          "description": "Two comma-separated inks: cobalt, vermilion, plum, sunflower, cornflower or black."
        },
        {
          "name": "offset",
          "type": "number",
          "default": "2",
          "description": "Misregistration of the second ink in pixels (0 to 6)."
        },
        {
          "name": "halftone",
          "type": "boolean",
          "default": "absent",
          "description": "Print the second ink as a halftone dot screen."
        },
        {
          "name": "caption",
          "type": "string",
          "default": "—",
          "description": "Caption text."
        },
        {
          "name": "credit",
          "type": "string",
          "default": "—",
          "description": "Credit line appended to the caption."
        }
      ]
    },
    {
      "name": "tk-overprint",
      "description": "Display words or numbers printed twice, in two inks off register, overprinting where the passes meet. Inks switch to their night shades on riso-night.",
      "attributes": [
        {
          "name": "inks",
          "type": "string",
          "default": "cobalt,vermilion",
          "description": "Two comma-separated inks: cobalt, vermilion, plum, sunflower, cornflower or black."
        },
        {
          "name": "offset",
          "type": "number",
          "default": "0.05",
          "description": "Misregistration in em (0 to 0.15)."
        }
      ]
    },
    {
      "name": "tk-tear",
      "description": "Alias of tk-torn, kept for sheets written from older instructions: it becomes a tk-torn with the same attributes. Write tk-torn.",
      "attributes": [
        {
          "name": "paper",
          "type": "enum kraft|cream|cobalt|vermilion|plum|sunflower",
          "default": "kraft",
          "description": "As on tk-torn."
        }
      ]
    },
    {
      "name": "tk-signature",
      "description": "Signature status and signer actions from the request embed JSON. Private bearer links must be shared only with intended signers.",
      "attributes": [
        {
          "name": "request",
          "type": "string",
          "default": "",
          "description": "Signature request ID. Include the API-returned JSON child with signer links."
        }
      ]
    },
    {
      "name": "tk-canvas",
      "description": "A bounded board (pan, pinch and Ctrl/Cmd+wheel zoom, Fit, arrow keys, + and -, a minimap) whose children with x and y are placed on it: tk-screen, tk-note or any element. Arrows are drawn from data-tk-go links between items and from tk-link; their labels and strokes keep their size at any zoom. With start it is a prototype and locked: a tap on a link glides the camera to the next screen, and Simulate dims the page and shows one screen at a time, always from the start screen (Back, Restart, Exit, Esc; a tap on nothing flashes what can be tapped). Without start it is a board: press and hold (touch) or drag (mouse) moves any item and Reset layout puts everything back; on the owner’s own page Save keeps the layout as this version or a new one; where replies are on, readers add notes of their own (+ Note) and send them as one reply (payload kind \"notes\": canvas, notes [{ text, x, y, near }], and the same in words, each note with the item it sits nearest). On a phone it stays still until Tap to explore, so it never takes the page’s swipes. Its height is --tk-canvas-height (560px).",
      "attributes": [
        {
          "name": "width",
          "type": "number",
          "default": "4000",
          "description": "Board width in CSS pixels (200–20000)."
        },
        {
          "name": "height",
          "type": "number",
          "default": "3000",
          "description": "Board height in CSS pixels (200–20000). The canvas on the page is --tk-canvas-height tall."
        },
        {
          "name": "start",
          "type": "id-ref",
          "default": "—",
          "description": "The tk-screen a prototype starts on. Makes the canvas a prototype: items stay where they are, and Simulate and tk-task use this screen first."
        },
        {
          "name": "label",
          "type": "string",
          "default": "Prototype or Canvas",
          "description": "Accessible name of the canvas."
        },
        {
          "name": "arrows",
          "type": "enum auto|none",
          "default": "auto",
          "description": "none draws no arrows (links still work)."
        },
        {
          "name": "notes",
          "type": "enum on|off",
          "default": "on",
          "description": "A board only: off leaves out + Note, so readers cannot send notes from it."
        }
      ]
    },
    {
      "name": "tk-screen",
      "description": "One screen of a prototype on a tk-canvas: a device frame around HTML, inline SVG or an image, its name above it. The content scrolls inside the frame when it is taller. The page’s theme and style never reach inside a screen or a note: it starts from plain browser defaults, and the sheet’s own CSS (scoped under tk-screen) styles it. Build screens from HTML, SVG and images; kit components inside a screen get no kit styling.",
      "attributes": [
        {
          "name": "x",
          "type": "number",
          "default": "required",
          "description": "Left edge on the board, in CSS pixels."
        },
        {
          "name": "y",
          "type": "number",
          "default": "required",
          "description": "Top edge on the board."
        },
        {
          "name": "name",
          "type": "string",
          "default": "id",
          "description": "Name shown above the screen and in Simulate."
        },
        {
          "name": "device",
          "type": "enum phone|tablet|desktop|none",
          "default": "phone",
          "description": "Frame and size: phone 390×844, tablet 820×1180, desktop 1280×800, none a plain border."
        },
        {
          "name": "width",
          "type": "number",
          "default": "device width",
          "description": "Screen width in CSS pixels (120–4000)."
        },
        {
          "name": "height",
          "type": "number",
          "default": "device height",
          "description": "Screen height in CSS pixels (120–4000)."
        },
        {
          "name": "data-tk-go",
          "type": "id-ref",
          "default": "—",
          "description": "Goes on any element inside a tk-screen (a button, a link, a tk-hotspot), not on the screen itself. A tap goes to that screen (the camera glides there) and the canvas draws an arrow for it."
        },
        {
          "name": "data-tk-back",
          "type": "boolean",
          "default": "absent",
          "description": "Goes on any element inside a tk-screen (a button, a link, a tk-hotspot), not on the screen itself. A tap goes back to the screen before."
        },
        {
          "name": "data-tk-set",
          "type": "string",
          "default": "—",
          "description": "Goes on any element inside a tk-screen (a button, a link, a tk-hotspot), not on the screen itself. \"name=value\" pairs separated by ; set on tap; the canvas keeps them until Restart."
        },
        {
          "name": "data-tk-show",
          "type": "string",
          "default": "—",
          "description": "On any element in the canvas: shows the value of that name."
        },
        {
          "name": "data-tk-default",
          "type": "string",
          "default": "\"\"",
          "description": "With data-tk-show: the words shown before the name is set."
        },
        {
          "name": "data-tk-pressed-when",
          "type": "string",
          "default": "—",
          "description": "On any element in the canvas: \"name=value\" sets aria-pressed=\"true\" while the name has that value (style [aria-pressed=\"true\"])."
        }
      ]
    },
    {
      "name": "tk-hotspot",
      "description": "A tappable rectangle on a tk-screen, in percent of the screen, for images and SVG mock-ups that have no buttons of their own. Give it data-tk-go, data-tk-back or data-tk-set. It is a button for the keyboard and screen readers.",
      "attributes": [
        {
          "name": "x",
          "type": "number",
          "default": "0",
          "description": "Left edge, percent of the screen width."
        },
        {
          "name": "y",
          "type": "number",
          "default": "0",
          "description": "Top edge, percent of the screen height."
        },
        {
          "name": "w",
          "type": "number",
          "default": "10",
          "description": "Width, percent of the screen width."
        },
        {
          "name": "h",
          "type": "number",
          "default": "5",
          "description": "Height, percent of the screen height."
        },
        {
          "name": "label",
          "type": "string",
          "default": "Go to <screen>",
          "description": "Accessible name."
        }
      ]
    },
    {
      "name": "tk-note",
      "description": "A paper note on a tk-canvas: a short remark, question or decision beside the screens or cards. Readers can move it on a board.",
      "attributes": [
        {
          "name": "x",
          "type": "number",
          "default": "required",
          "description": "Left edge on the board."
        },
        {
          "name": "y",
          "type": "number",
          "default": "required",
          "description": "Top edge on the board."
        },
        {
          "name": "w",
          "type": "number",
          "default": "230",
          "description": "Width in CSS pixels."
        },
        {
          "name": "color",
          "type": "enum sunflower|blush|cornflower|kraft|frost",
          "default": "sunflower",
          "description": "Paper colour (the same on every stock)."
        }
      ]
    },
    {
      "name": "tk-link",
      "description": "An arrow on a tk-canvas between two items that no data-tk-go link joins, with an optional label. Draws nothing on its own.",
      "attributes": [
        {
          "name": "from",
          "type": "id-ref",
          "default": "required",
          "description": "Item the arrow starts at."
        },
        {
          "name": "to",
          "type": "id-ref",
          "default": "required",
          "description": "Item the arrow points to."
        },
        {
          "name": "label",
          "type": "string",
          "default": "—",
          "description": "Words on the arrow (at most 40 characters)."
        }
      ]
    },
    {
      "name": "tk-task",
      "description": "A light usability test on a prototype tk-canvas. It says first what it records; Start opens Simulate at the start screen and records only the screens visited, taps on nothing and the time, until the goal screen or I give up. The result goes to the owner as one reply (payload kind \"task\" with task, completed, seconds, path, deadTaps, screensVisited, and the same in words); where replies are off, or outside Tacksheet, it shows on the page instead. Its text is the task as the reader reads it.",
      "attributes": [
        {
          "name": "for",
          "type": "id-ref",
          "default": "required",
          "description": "The id of a tk-canvas with start."
        },
        {
          "name": "goal",
          "type": "id-ref",
          "default": "required",
          "description": "The tk-screen that completes the task."
        },
        {
          "name": "name",
          "type": "string",
          "default": "the task text",
          "description": "Short name sent with the result, so results of one task group together."
        },
        {
          "name": "expected-revision",
          "type": "number",
          "default": "shell value",
          "description": "Revision sent with the submission."
        }
      ]
    },
    {
      "name": "tk-strip",
      "description": "A row that scrolls sideways inside the sheet, for content that is a row by nature: boards, timelines, before and after, screens. Each child is one card; swipe, Shift+wheel, the arrow buttons or the arrow keys move it, and \"1–3 of 6\" says where you are. The page itself never scrolls sideways.",
      "attributes": [
        {
          "name": "label",
          "type": "string",
          "default": "Strip",
          "description": "Accessible name of the row."
        },
        {
          "name": "snap",
          "type": "boolean",
          "default": "absent",
          "description": "Each swipe lands on a card."
        },
        {
          "name": "item-width",
          "type": "string",
          "default": "min(15rem, 80%)",
          "description": "Card width: a CSS length such as 18rem or 280 (pixels); also --tk-strip-item."
        }
      ]
    }
  ],
  "chartTypes": [
    {
      "type": "line",
      "description": "Line chart over categories.",
      "data": "Wide: {labels, datasets:[{label,data}]} or rows with x/y."
    },
    {
      "type": "area",
      "description": "Line chart with filled area.",
      "data": "Same as line."
    },
    {
      "type": "stacked-area",
      "description": "Stacked areas sharing one baseline.",
      "data": "Same as line, 2+ series."
    },
    {
      "type": "bar",
      "description": "Vertical bars.",
      "data": "Same as line."
    },
    {
      "type": "horizontal-bar",
      "description": "Horizontal bars for long labels.",
      "data": "Same as line."
    },
    {
      "type": "stacked-bar",
      "description": "Bars stacked to a total.",
      "data": "Same as line, 2+ series."
    },
    {
      "type": "grouped-bar",
      "description": "Side-by-side bars (alias of bar).",
      "data": "Same as line."
    },
    {
      "type": "percent-bar",
      "description": "Stacked bars normalized to 100%.",
      "data": "Same as line, 2+ series."
    },
    {
      "type": "scatter",
      "description": "XY points.",
      "data": "{points:[[x,y]]} or rows with x/y."
    },
    {
      "type": "bubble",
      "description": "XY points sized by a third value.",
      "data": "{points:[[x,y,size]]} or rows with x/y/size."
    },
    {
      "type": "histogram",
      "description": "Binned value distribution (bins attr, default 12).",
      "data": "{values:[…]}."
    },
    {
      "type": "box",
      "description": "Box plot with quartiles computed in the kit.",
      "data": "{groups:[{name,values}]} or rows with series/y."
    },
    {
      "type": "violin",
      "description": "KDE violin with an inner box plot.",
      "data": "{groups:[{name,values}]}."
    },
    {
      "type": "heatmap",
      "description": "Matrix heatmap.",
      "data": "{x:[…], y:[…], values:[[xi,yi,v]]}."
    },
    {
      "type": "calendar-heatmap",
      "description": "Year calendar heatmap.",
      "data": "{values:[[dateString, v]]}."
    },
    {
      "type": "treemap",
      "description": "Nested rectangles.",
      "data": "{children:[{name,value,children?}]}."
    },
    {
      "type": "sunburst",
      "description": "Radial hierarchy.",
      "data": "Same as treemap."
    },
    {
      "type": "sankey",
      "description": "Flow diagram.",
      "data": "{nodes:[{name}], links:[{source,target,value}]} (names or indices)."
    },
    {
      "type": "network",
      "description": "Force-directed graph (alias: graph).",
      "data": "{nodes:[{id?,name,category?}], links:[{source,target}], categories?}."
    },
    {
      "type": "graph",
      "description": "Alias of network.",
      "data": "Same as network."
    },
    {
      "type": "chord",
      "description": "Approximation: ECharts 5 has no chord series, so a circular node-link graph stands in. Node size follows each row total and link width the square root of the upper-triangle value; direction and arc ribbons are not drawn.",
      "data": "{labels:[…], matrix:[[n]]}."
    },
    {
      "type": "radar",
      "description": "Spider chart.",
      "data": "{indicators:[{name,max}], wide series}."
    },
    {
      "type": "polar-bar",
      "description": "Bars on a polar grid.",
      "data": "Wide single series."
    },
    {
      "type": "candlestick",
      "description": "Candlesticks: a body from open to close and a wick from low to high (alias: ohlc).",
      "data": "{x:[…], values:[[open,close,low,high]]} or rows with open/close/low/high."
    },
    {
      "type": "ohlc",
      "description": "Approximation: an alias of candlestick, so the same values draw as candles, not as bars with open and close ticks.",
      "data": "Same as candlestick."
    },
    {
      "type": "waterfall",
      "description": "Running-total bars; the last value is the total.",
      "data": "{labels, values:[signed deltas…, total]}."
    },
    {
      "type": "funnel",
      "description": "Stage funnel.",
      "data": "[{name,value}] or {values:[…]}."
    },
    {
      "type": "gauge",
      "description": "Single-value gauge.",
      "data": "{value, min?, max?, name?}."
    },
    {
      "type": "pie",
      "description": "Pie chart.",
      "data": "Named values [{name,value}]."
    },
    {
      "type": "donut",
      "description": "Pie with a hole.",
      "data": "Same as pie."
    },
    {
      "type": "rose",
      "description": "Nightingale rose (area).",
      "data": "Same as pie."
    },
    {
      "type": "sparkline",
      "description": "Minimal inline trend, no axes.",
      "data": "{values:[…]}."
    },
    {
      "type": "bullet",
      "description": "Performance bullet: value bar, target tick, qualitative bands.",
      "data": "{value, target, ranges:[…], label?}."
    },
    {
      "type": "dumbbell",
      "description": "Paired dots joined by a line.",
      "data": "Rows with a/b (attrs) or {labels, pairs:[[a,b]]} or two wide series."
    },
    {
      "type": "slope",
      "description": "Slopegraph between two categories.",
      "data": "Wide series; only the first two labels are drawn."
    },
    {
      "type": "lollipop",
      "description": "Thin stems with dots.",
      "data": "Wide single series."
    },
    {
      "type": "parallel-coordinates",
      "description": "Parallel-coordinates plot (alias: parallel).",
      "data": "{dimensions:[…], data:[[v…]]}."
    },
    {
      "type": "parallel",
      "description": "Alias of parallel-coordinates.",
      "data": "Same as parallel-coordinates."
    },
    {
      "type": "themeriver",
      "description": "Streamgraph over time (alias: streamgraph).",
      "data": "{values:[[time, value, name]]}."
    },
    {
      "type": "streamgraph",
      "description": "Alias of themeriver.",
      "data": "Same as themeriver."
    },
    {
      "type": "timeline",
      "description": "Gantt bars on a time axis (alias: gantt).",
      "data": "{tasks:[{name,start,end}]} (ISO dates)."
    },
    {
      "type": "gantt",
      "description": "Alias of timeline.",
      "data": "Same as timeline."
    },
    {
      "type": "choropleth",
      "description": "Filled map (map attr: usa|world); matches data names to features case-insensitively.",
      "data": "{values:[{name, value}]}."
    },
    {
      "type": "dot-map",
      "description": "Scaled dots on a map (aliases: dotmap).",
      "data": "{points:[{name, lon, lat, value?}]}."
    },
    {
      "type": "dotmap",
      "description": "Alias of dot-map.",
      "data": "Same as dot-map."
    },
    {
      "type": "contour",
      "description": "Approximation: a Gaussian-smoothed density heatmap on a bins×bins grid with hidden axes, not isolines (alias: density).",
      "data": "{points:[[x,y]]}, bins attr (default 24)."
    },
    {
      "type": "density",
      "description": "Alias of contour (the same density-heatmap approximation).",
      "data": "Same as contour."
    },
    {
      "type": "error-bars",
      "description": "Bars with whiskers (alias: errorbars).",
      "data": "Rows with y/error (attrs) or {labels, values, errors}."
    },
    {
      "type": "errorbars",
      "description": "Alias of error-bars.",
      "data": "Same as error-bars."
    },
    {
      "type": "regression",
      "description": "Scatter with a least-squares fit line.",
      "data": "{points:[[x,y]]} or rows with x/y."
    },
    {
      "type": "combo",
      "description": "Mixed bar+line with an optional second axis (kinds/axes attrs).",
      "data": "Wide series; kinds=\"bar,line\", axes=\"0,1\"."
    }
  ],
  "bridge": {
    "protocol": "tacksheet-sheet-v1",
    "sheetToParent": [
      {
        "kind": "resize",
        "shape": "{ protocol, kind: \"resize\", type: \"resize\", height: number }",
        "description": "Posted on load, resize and DOM changes so the shell can size the iframe without inner scrollbars (Tacksheet shells honour up to 100,000 px)."
      },
      {
        "kind": "viewport.watch",
        "shape": "{ protocol, kind: \"viewport.watch\", type: \"viewport.watch\" }",
        "description": "Posted once on load by an embedded sheet: asks the shell to report its visible window (viewport) on every scroll and resize, since the sheet never scrolls itself."
      },
      {
        "kind": "feedback.status",
        "shape": "{ protocol, kind: \"feedback.status\", type: \"feedback.status\" }",
        "description": "Asks whether the shell accepts reader feedback right now."
      },
      {
        "kind": "feedback.submit",
        "shape": "{ protocol, kind: \"feedback.submit\", type: \"feedback.submit\", token: string, payload: object, expectedRevision: number }",
        "description": "Hands a reader submission to the shell. The shell shows its own Send dialog (with the terms while this reader has not accepted them) and POSTs only after the reader presses Send there; the sheet can neither accept the terms nor skip the dialog. Free text is limited to 4,000 characters and is refused whole (text_too_long, with limit), never shortened. expectedRevision is the responseRevision last heard from the shell. Token matches the async result."
      },
      {
        "kind": "file.open",
        "shape": "{ protocol, kind: \"file.open\", type: \"file.open\", name: string }",
        "description": "Asks the shell to show a download/open button for an allowlisted same-version file."
      },
      {
        "kind": "file.fetch",
        "shape": "{ protocol, kind: \"file.fetch\", type: \"file.fetch\", token: string, name: string }",
        "description": "Asks the shell for a same-review file’s bytes (sheets have connect-src ‘none’ and cannot fetch). Name is the file within the version, without any ./files/ prefix. Token matches the async result. The kit keeps two in flight and retries busy answers."
      },
      {
        "kind": "signature.open",
        "shape": "{path}",
        "description": "Open a private signer path or completed PDF through the trusted shell."
      },
      {
        "kind": "signature.status",
        "shape": "{path,token}",
        "description": "Read the status of a signer capability; no owner credentials."
      },
      {
        "kind": "overlay.state",
        "shape": "{ protocol, kind: \"overlay.state\", type: \"overlay.state\", open: boolean }",
        "description": "Posted when the image viewer opens and closes, so the shell can keep its own floating buttons out of the way and hold its page still."
      },
      {
        "kind": "feedback.draft.load",
        "shape": "{ protocol, kind: \"feedback.draft.load\", type: \"feedback.draft.load\", token: string, key: string }",
        "description": "Asks the shell for one reply widget’s unsent answers. key names the widget only (its tag and id, or its place among its kind); the shell keeps drafts under the sheet, version and link it shows, which the sheet cannot choose. A shell that keeps no drafts does not answer, and after 1.5 s the widget works as in 1.5.0."
      },
      {
        "kind": "feedback.draft.save",
        "shape": "{ protocol, kind: \"feedback.draft.save\", type: \"feedback.draft.save\", key: string, data: object }",
        "description": "Hands the shell a widget’s current answers as plain JSON data, half a second after the reader stops typing. The shell keeps them in this browser only (at most 16 KiB each, for seven days), writes at most every half second per widget, and never sends them to the server."
      },
      {
        "kind": "feedback.draft.clear",
        "shape": "{ protocol, kind: \"feedback.draft.clear\", type: \"feedback.draft.clear\", key: string }",
        "description": "Drops a widget’s unsent answers: after a saved reply, on Start over, or when the reader empties every field."
      },
      {
        "kind": "layout.status",
        "shape": "{ protocol, kind: \"layout.status\", type: \"layout.status\" }",
        "description": "Posted once by a sheet with a board (a tk-canvas without start that has an id): may this reader save its layout? A shell that does not answer within 2 s means no, and the board shows no Save button."
      },
      {
        "kind": "layout.save",
        "shape": "{ protocol, kind: \"layout.save\", type: \"layout.save\", token: string, canvas: string, items: { [item id]: { x: number, y: number } }, mode?: \"replace\" | \"new\" }",
        "description": "The owner pressed Save on a board: the tk-canvas id and the whole-pixel position of every item on it with an id a layout can keep (a letter, then letters, digits, _ : . or -, 100 at most; 500 items at most). The shell then asks in its own dialog, which the sheet cannot reach: replace keeps this version (unless it has replies, when the shell keeps it and makes a new one), new makes a new version. mode only picks which choice the dialog focuses; nothing is saved until the owner chooses. Token matches the async result."
      }
    ],
    "parentToSheet": [
      {
        "kind": "feedback.status.result",
        "shape": "{ protocol, kind: \"feedback.status.result\", type: \"feedback.status.result\", accepting: boolean, schemaRevision: number, responseRevision: number, mine?: boolean, reason?: \"owner_preview\" | \"unavailable\" }",
        "description": "Shell answer to feedback.status, and pushed again after every saved reply. responseRevision counts this browser’s saved replies; accepted (read-only) says whether this reader already accepted the current terms, so a sheet can word its button; reason owner_preview marks the owner’s own preview. From 2026-10-14 shells say reason \"unavailable\" when they could not load the sheet’s replies (not closed: the page offers Retry replies and answers again once they load)."
      },
      {
        "kind": "viewport",
        "shape": "{ protocol, kind: \"viewport\", type: \"viewport\", top: number, height: number, left?: number, width?: number, scale?: number }",
        "description": "Shell answer to viewport.watch, posted on every scroll, resize and frame resize and when the frame comes into view: the page’s visible window in the sheet’s own CSS pixels. top is negative while the sheet starts lower down the page. Shells from 2026-10-13 add left and width (the visible part, also while the reader is pinch-zoomed) and scale (the page’s pinch zoom). tk-scrolly keeps its chart beside the step being read; the image viewer covers exactly the visible part."
      },
      {
        "kind": "feedback.submit.result",
        "shape": "{ protocol, kind: \"feedback.submit.result\", type: \"feedback.submit.result\", token: string, ok: boolean, responseRevision?: number, error?: string, limit?: number }",
        "description": "Shell answer to feedback.submit, matched by token. error is cancelled when the reader closes the dialog, busy while another dialog is open, rate_limited after many in a minute, and text_too_long (with limit) for an over-long reply."
      },
      {
        "kind": "file.fetch.result",
        "shape": "{ protocol, kind: \"file.fetch.result\", type: \"file.fetch.result\", token: string, ok: boolean, name?: string, mediaType?: string, size?: number, bytes?: ArrayBuffer, error?: string }",
        "description": "Shell answer to file.fetch, matched by token. Bytes travel as a transferred ArrayBuffer; the kit turns them into a blob: URL (media) or parses them (GLB). ok:false carries error instead."
      },
      {
        "kind": "signature.status.result",
        "shape": "{token,data}",
        "description": "Current request/signers status or null when the link is unavailable."
      },
      {
        "kind": "feedback.submit.pending",
        "shape": "{ protocol, kind: \"feedback.submit.pending\", type: \"feedback.submit.pending\", token: string }",
        "description": "Posted when the shell’s Send dialog opens for a feedback.submit, so the sheet waits for the reader instead of timing out after 15 s. Shells from 2026-10-14 send it to every kit; older kits ignore it."
      },
      {
        "kind": "feedback.draft.result",
        "shape": "{ protocol, kind: \"feedback.draft.result\", type: \"feedback.draft.result\", token: string, key: string, ok: boolean, data?: object | null, savedAt?: number, error?: string }",
        "description": "Shell answer to feedback.draft.load, matched by token: the kept answers (null when there are none) and when they were saved (ms since the epoch). The kit fills only empty fields, and only with values the widget still offers."
      },
      {
        "kind": "feedback.draft.saved",
        "shape": "{ protocol, kind: \"feedback.draft.saved\", type: \"feedback.draft.saved\", key: string, ok: boolean, error?: \"too_large\" | \"too_many\" | \"invalid\" | \"invalid_key\" | \"storage_full\" | \"unavailable\" }",
        "description": "Shell answer to feedback.draft.save once it has written (or refused) the draft; the widget then shows \"Answers saved on this device\"."
      },
      {
        "kind": "layout.status.result",
        "shape": "{ protocol, kind: \"layout.status.result\", type: \"layout.status.result\", saving: boolean }",
        "description": "Shell answer to layout.status: saving is true only on the owner’s own page of a version that can be saved."
      },
      {
        "kind": "layout.save.result",
        "shape": "{ protocol, kind: \"layout.save.result\", type: \"layout.save.result\", token: string, ok: boolean, error?: string, message?: string }",
        "description": "Shell answer to layout.save, matched by token. error is cancelled when the owner closes the dialog and busy while another save is open. On ok the shell opens the saved version; otherwise the board shows message (or words for error) and keeps the reader’s layout."
      }
    ]
  },
  "defaultTheme": "riso",
  "materials": {
    "tapes": [
      {
        "name": "kraft",
        "color": "#C9B48F",
        "alpha": 0.92,
        "blend": "multiply"
      },
      {
        "name": "frost",
        "color": "#F6F3EC",
        "alpha": 0.72,
        "blend": "normal"
      },
      {
        "name": "vermilion",
        "color": "#E64B35",
        "alpha": 0.86,
        "blend": "multiply"
      },
      {
        "name": "cobalt",
        "color": "#2B3F8D",
        "alpha": 0.9,
        "blend": "multiply"
      },
      {
        "name": "plum",
        "color": "#6B2F5E",
        "alpha": 0.9,
        "blend": "multiply"
      },
      {
        "name": "sunflower",
        "color": "#F2B33D",
        "alpha": 0.86,
        "blend": "multiply"
      },
      {
        "name": "cornflower",
        "color": "#9DB0F0",
        "alpha": 0.86,
        "blend": "multiply"
      },
      {
        "name": "blush",
        "color": "#F2A7A0",
        "alpha": 0.84,
        "blend": "multiply"
      }
    ],
    "tapePatterns": [
      "plain",
      "grid",
      "dots",
      "stripes",
      "kraft"
    ],
    "titleInks": {
      "cobalt": "#2B3F8D",
      "black": "#1E1E1E",
      "cream": "#F4EEE0",
      "vermilion": "#E64B35",
      "plum": "#6B2F5E"
    },
    "inks": {
      "cobalt": {
        "day": "#2B3F8D",
        "night": "#9DB0F0"
      },
      "vermilion": {
        "day": "#E64B35",
        "night": "#FF6B4F"
      },
      "plum": {
        "day": "#6B2F5E",
        "night": "#D896C8"
      },
      "sunflower": {
        "day": "#F2B33D",
        "night": "#F2B33D"
      },
      "cornflower": {
        "day": "#9DB0F0",
        "night": "#9DB0F0"
      },
      "black": {
        "day": "#1E1E1E",
        "night": "#F1E9D6"
      }
    },
    "contrast": "Tape titles: the ink must reach 4.5:1 against the tape as it lands on the page (3:1 at 24px and up, or 18.66px bold); a failing ink is replaced by cobalt, near-black or cream."
  },
  "bare": {
    "css": "/kit/1.8.0/tacksheet-base.css",
    "theme": "none"
  },
  "tokens": [
    {
      "name": "--tk-bg",
      "group": "colour",
      "description": "Page stock: the colour behind everything."
    },
    {
      "name": "--tk-bg-soft",
      "group": "colour",
      "description": "Subtle surface: table stripes, chips, disabled buttons, the riso reply tab."
    },
    {
      "name": "--tk-fg",
      "group": "colour",
      "description": "Body text and heading colour."
    },
    {
      "name": "--tk-muted",
      "group": "colour",
      "description": "Secondary text: notes, captions, labels, axis text."
    },
    {
      "name": "--tk-accent",
      "group": "colour",
      "description": "Links, focus rings, selected controls, primary buttons and the default chart accent."
    },
    {
      "name": "--tk-accent-2",
      "group": "colour",
      "description": "Second ink for rules, markers and list numbers; small text in it uses a darkened mix (--tk-accent-2-text)."
    },
    {
      "name": "--tk-accent-ink",
      "group": "colour",
      "description": "Text on accent fills, such as primary buttons."
    },
    {
      "name": "--tk-card",
      "group": "colour",
      "description": "Cards, callouts, stats, chart frames and tooltips."
    },
    {
      "name": "--tk-border",
      "group": "colour",
      "description": "Hairlines and quiet borders."
    },
    {
      "name": "--tk-rule",
      "group": "colour",
      "description": "Strong rules: hero and section rules, table heads, framed boxes."
    },
    {
      "name": "--tk-mark",
      "group": "colour",
      "description": "Washes: highlighted rows and the active step."
    },
    {
      "name": "--tk-field",
      "group": "colour",
      "description": "Form field background."
    },
    {
      "name": "--tk-control",
      "group": "colour",
      "description": "Form-control boundary (keep 3:1 against fields); falls back to --tk-border."
    },
    {
      "name": "--tk-positive",
      "group": "colour",
      "description": "Good deltas and success states."
    },
    {
      "name": "--tk-negative",
      "group": "colour",
      "description": "Bad deltas and error states."
    },
    {
      "name": "--tk-note",
      "group": "colour",
      "description": "tk-callout kind=\"note\": its rule and stamped label."
    },
    {
      "name": "--tk-tip",
      "group": "colour",
      "description": "tk-callout kind=\"tip\": its rule and stamped label."
    },
    {
      "name": "--tk-warning",
      "group": "colour",
      "description": "tk-callout kind=\"warning\": its rule and stamped label."
    },
    {
      "name": "--tk-decision",
      "group": "colour",
      "description": "tk-callout kind=\"decision\": its rule and stamped label."
    },
    {
      "name": "--tk-risk",
      "group": "colour",
      "description": "tk-callout kind=\"risk\": its rule and stamped label."
    },
    {
      "name": "--tk-head",
      "group": "colour",
      "description": "Heading ink of the riso themes (full kit only)."
    },
    {
      "name": "--tk-misreg",
      "group": "colour",
      "description": "Off-register second pass behind riso headings (full kit only)."
    },
    {
      "name": "--tk-ink-cobalt",
      "group": "colour",
      "description": "The cobalt ink in this stock's shade, read by tk-mark, tk-duotone, tk-overprint and tape titles."
    },
    {
      "name": "--tk-ink-vermilion",
      "group": "colour",
      "description": "The vermilion ink in this stock's shade, read by tk-mark, tk-duotone, tk-overprint and tape titles."
    },
    {
      "name": "--tk-ink-plum",
      "group": "colour",
      "description": "The plum ink in this stock's shade, read by tk-mark, tk-duotone, tk-overprint and tape titles."
    },
    {
      "name": "--tk-ink-sunflower",
      "group": "colour",
      "description": "The sunflower ink in this stock's shade, read by tk-mark, tk-duotone, tk-overprint and tape titles."
    },
    {
      "name": "--tk-ink-cornflower",
      "group": "colour",
      "description": "The cornflower ink in this stock's shade, read by tk-mark, tk-duotone, tk-overprint and tape titles."
    },
    {
      "name": "--tk-ink-black",
      "group": "colour",
      "description": "The black ink in this stock's shade, read by tk-mark, tk-duotone, tk-overprint and tape titles."
    },
    {
      "name": "--tk-stamp-cobalt",
      "group": "colour",
      "description": "tk-stamp ink=\"cobalt\"."
    },
    {
      "name": "--tk-stamp-vermilion",
      "group": "colour",
      "description": "tk-stamp ink=\"vermilion\"."
    },
    {
      "name": "--tk-stamp-plum",
      "group": "colour",
      "description": "tk-stamp ink=\"plum\"."
    },
    {
      "name": "--tk-mark-fg",
      "group": "colour",
      "description": "Text colour inside tk-mark highlights (currentColor by default; dark type on dark stock)."
    },
    {
      "name": "--tk-mark-sunflower",
      "group": "colour",
      "description": "tk-mark ink=\"sunflower\" highlighter wash."
    },
    {
      "name": "--tk-mark-vermilion",
      "group": "colour",
      "description": "tk-mark ink=\"vermilion\" highlighter wash."
    },
    {
      "name": "--tk-mark-cobalt",
      "group": "colour",
      "description": "tk-mark ink=\"cobalt\" highlighter wash."
    },
    {
      "name": "--tk-mark-plum",
      "group": "colour",
      "description": "tk-mark ink=\"plum\" highlighter wash."
    },
    {
      "name": "--tk-mark-cornflower",
      "group": "colour",
      "description": "tk-mark ink=\"cornflower\" highlighter wash."
    },
    {
      "name": "--tk-font-head",
      "group": "type",
      "description": "Heading face (a font-family list)."
    },
    {
      "name": "--tk-font-body",
      "group": "type",
      "description": "Body face."
    },
    {
      "name": "--tk-font-mono",
      "group": "type",
      "description": "Code, keys and small label face."
    },
    {
      "name": "--tk-font-data",
      "group": "type",
      "description": "Figures: stats, tables and chart text."
    },
    {
      "name": "--tk-font-hand",
      "group": "type",
      "description": "Handwriting: slips, print captions and hand-lettered tape titles."
    },
    {
      "name": "--tk-head-weight",
      "group": "type",
      "description": "Heading weight."
    },
    {
      "name": "--tk-head-tracking",
      "group": "type",
      "description": "Heading letter-spacing."
    },
    {
      "name": "--tk-head-leading",
      "group": "type",
      "description": "Heading line-height."
    },
    {
      "name": "--tk-size-body",
      "group": "type",
      "description": "Body font size."
    },
    {
      "name": "--tk-size-h1",
      "group": "type",
      "description": "h1 size (clamp() works)."
    },
    {
      "name": "--tk-size-h2",
      "group": "type",
      "description": "h2 size."
    },
    {
      "name": "--tk-size-h3",
      "group": "type",
      "description": "h3 size."
    },
    {
      "name": "--tk-radius",
      "group": "shape",
      "description": "Corner radius of cards, fields, buttons and frames."
    },
    {
      "name": "--tk-chart-radius",
      "group": "shape",
      "description": "Rounded data end of chart bars, as a plain number of pixels."
    },
    {
      "name": "--tk-shadow",
      "group": "shape",
      "description": "Resting shadow of cards and stats (none for flat)."
    },
    {
      "name": "--tk-lift",
      "group": "shape",
      "description": "Raised shadow on hover and for lifted pieces."
    },
    {
      "name": "--tk-density",
      "group": "shape",
      "description": "Spacing multiplier (1 by default; 0.85 is tighter)."
    },
    {
      "name": "--tk-page-width",
      "group": "layout",
      "description": "Width of the tk-page column; min(max(43rem,76vw),110rem) by default. The riso reading panel adds 2.5rem for its padding."
    },
    {
      "name": "--tk-page-wide",
      "group": "layout",
      "description": "Width of tk-page[wide]; min(max(76rem,94vw),140rem) by default."
    },
    {
      "name": "--tk-measure",
      "group": "layout",
      "description": "Longest line of running text (paragraphs, lists, quotes, callouts); 46rem by default."
    },
    {
      "name": "--tk-canvas-height",
      "group": "layout",
      "description": "Height of a tk-canvas on the page; 560px by default. Simulate makes it at least 80% of the window."
    },
    {
      "name": "--tk-strip-item",
      "group": "layout",
      "description": "Width of each card in a tk-strip; min(15rem, 80%) by default (item-width sets it per strip)."
    },
    {
      "name": "--tk-texture",
      "group": "material",
      "description": "Background-image layers on body: paper, grain, a grid or a gradient; none for a flat page."
    },
    {
      "name": "--tk-texture-size",
      "group": "material",
      "description": "background-size of each --tk-texture layer."
    },
    {
      "name": "--tk-texture-repeat",
      "group": "material",
      "description": "background-repeat of each layer (repeat by default)."
    },
    {
      "name": "--tk-texture-blend",
      "group": "material",
      "description": "background-blend-mode of each layer (normal by default)."
    },
    {
      "name": "--tk-film",
      "group": "material",
      "description": "How tape films, stamps and overprints meet the stock: multiply on light stock, screen on dark."
    },
    {
      "name": "--tk-paper-obj",
      "group": "material",
      "description": "The cream of slips, taped prints and duotone paper."
    },
    {
      "name": "--tk-kraft",
      "group": "material",
      "description": "Kraft paper of tk-torn strips."
    },
    {
      "name": "--tk-panel",
      "group": "panel",
      "description": "Reading panel behind tk-page in the riso themes of the full kit; transparent turns it off. No effect on the bare kit."
    },
    {
      "name": "--tk-panel-edge",
      "group": "panel",
      "description": "Border colour of the reading panel."
    },
    {
      "name": "--tk-panel-shadow",
      "group": "panel",
      "description": "Shadow of the reading panel."
    },
    {
      "name": "--tk-code-bg",
      "group": "code",
      "description": "tk-code background."
    },
    {
      "name": "--tk-code-fg",
      "group": "code",
      "description": "tk-code text."
    },
    {
      "name": "--tk-tok-k",
      "group": "code",
      "description": "Highlighted keywords and tags."
    },
    {
      "name": "--tk-tok-s",
      "group": "code",
      "description": "Highlighted strings."
    },
    {
      "name": "--tk-tok-c",
      "group": "code",
      "description": "Highlighted comments."
    },
    {
      "name": "--tk-tok-n",
      "group": "code",
      "description": "Highlighted numbers."
    },
    {
      "name": "--tk-tok-f",
      "group": "code",
      "description": "Highlighted attribute and function names."
    },
    {
      "name": "--tk-c1",
      "group": "chart",
      "description": "Categorical chart colour 1 (fixed order; keep adjacent pairs colour-blind safe)."
    },
    {
      "name": "--tk-c2",
      "group": "chart",
      "description": "Categorical chart colour 2 (fixed order; keep adjacent pairs colour-blind safe)."
    },
    {
      "name": "--tk-c3",
      "group": "chart",
      "description": "Categorical chart colour 3 (fixed order; keep adjacent pairs colour-blind safe)."
    },
    {
      "name": "--tk-c4",
      "group": "chart",
      "description": "Categorical chart colour 4 (fixed order; keep adjacent pairs colour-blind safe)."
    },
    {
      "name": "--tk-c5",
      "group": "chart",
      "description": "Categorical chart colour 5 (fixed order; keep adjacent pairs colour-blind safe)."
    },
    {
      "name": "--tk-c6",
      "group": "chart",
      "description": "Categorical chart colour 6 (fixed order; keep adjacent pairs colour-blind safe)."
    },
    {
      "name": "--tk-c7",
      "group": "chart",
      "description": "Categorical chart colour 7 (fixed order; keep adjacent pairs colour-blind safe)."
    },
    {
      "name": "--tk-c8",
      "group": "chart",
      "description": "Categorical chart colour 8 (fixed order; keep adjacent pairs colour-blind safe)."
    },
    {
      "name": "--tk-ramp",
      "group": "chart",
      "description": "Sequential ramp for heatmaps and maps: comma-separated colours, light to dark (dark to light on dark stock)."
    },
    {
      "name": "--tk-grid",
      "group": "chart",
      "description": "Chart grid lines."
    },
    {
      "name": "--tk-3d-sky",
      "group": "3d",
      "description": "Top of the 3D studio dome."
    },
    {
      "name": "--tk-3d-horizon",
      "group": "3d",
      "description": "Horizon and fog of the 3D studio."
    },
    {
      "name": "--tk-3d-ground",
      "group": "3d",
      "description": "3D floor."
    },
    {
      "name": "--tk-3d-grid",
      "group": "3d",
      "description": "3D floor grid lines."
    },
    {
      "name": "--tk-3d-terrain",
      "group": "3d",
      "description": "Terrain elevation ramp: comma-separated colours, low to high."
    },
    {
      "name": "--tk-3d-key",
      "group": "3d",
      "description": "Key-light colour."
    }
  ],
  "styles": {
    "path": "/styles/<style id>/<version>/style.css",
    "attribute": "data-tk-style",
    "description": "A style is a stylesheet tacksheet.com serves for one account: tokens on body{} plus its own CSS, no script. Link it right after the kit stylesheet, keep the body data-tk-theme of its base (none with tacksheet-base.css) and name it with data-tk-style=\"<style id>/<version>\" (a built-in style is named by its key). The tokens list is the contract a style may set."
  },
  "native": [
    {
      "name": "img",
      "description": "A content image (one with alt text, or any image in tk-gallery, tk-lightbox, tk-image, tk-figure, a figure, tk-taped or tk-duotone) opens the full-screen image viewer (previous and next across every viewable image on the page, a counter, the caption, swipe, pinch and double-tap zoom, Escape and arrow keys). Images in links, buttons, form controls, tk-zoom, tk-compare, tk-annotate, tk-model, tk-video and charts, decorative images (alt=\"\") and icons under 48px do not.",
      "attributes": [
        {
          "name": "data-tk-no-viewer",
          "type": "boolean",
          "default": "absent",
          "description": "Keep this image (or, on any ancestor, every image inside it; on body, the whole sheet) out of the viewer."
        }
      ]
    },
    {
      "name": "time",
      "description": "A <time datetime> shows the reader’s own date and time (\"11 Oct 2026, 11:37 am\" in their locale and time zone) with the exact time as a tooltip. Only an empty element, or one whose text is itself a raw ISO 8601 timestamp or epoch number, is rewritten; your own words stay. Never print raw ISO or epoch timestamps: wrap them in <time datetime>.",
      "attributes": [
        {
          "name": "datetime",
          "type": "string",
          "default": "text content",
          "description": "ISO 8601: 2026-10-11, 2026-10-11T16:37:08Z, 2026-10-11T16:37:08.211792+00:00, a local 2026-10-11T16:37 or 16:37, or epoch seconds or milliseconds."
        },
        {
          "name": "data-tk-format",
          "type": "enum datetime|date|time|relative|auto|none",
          "default": "datetime (date for a date-only value)",
          "description": "relative reads \"2 days ago\" and stays current; auto is relative within a week, else datetime; none leaves the element alone."
        }
      ]
    }
  ],
  "kitStyles": {
    "description": "Built-in styles served as kit files. Link styles/<key>.css right after the kit stylesheet (tacksheet-base.css for base none), keep the body data-tk-theme of its base and name it with data-tk-style=\"<key>\". Knobs are the attributes and custom properties the style reads; leave one out for its default. Notes say how to compose a sheet in the style.",
    "licences": "/kit/1.8.0/fonts/LICENSES-styles.txt",
    "items": [
      {
        "key": "aero",
        "name": "Aero",
        "description": "A 2006 desktop: glass windows, a taskbar with a start orb, gel buttons and a Bliss-style wallpaper.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/aero.css",
        "swatches": [
          "#0d5cb0",
          "#1f7ae0",
          "#3fa535",
          "#ffffff"
        ],
        "knobs": [
          {
            "names": [
              "data-aero-os"
            ],
            "on": "body",
            "values": [
              "vista",
              "xp",
              "aqua"
            ],
            "valuesText": null,
            "default": "vista",
            "does": "Window chrome, title bars, taskbar or dock and menu bar, and the default wallpaper."
          },
          {
            "names": [
              "data-aero-wallpaper"
            ],
            "on": "body",
            "values": [
              "aurora",
              "bliss",
              "bubbles",
              "aqua",
              "harmony"
            ],
            "valuesText": null,
            "default": "follows the OS (vista aurora, xp bliss, aqua aqua)",
            "does": "The desktop behind the windows."
          },
          {
            "names": [
              "data-aero-sky"
            ],
            "on": "body or tk-hero",
            "values": [
              "day",
              "sunset",
              "night",
              "ocean"
            ],
            "valuesText": null,
            "default": "day",
            "does": "The hero window's scene: blue sky and green hills; a dusk sky with purple hills; stars, a moon and dark hills; underwater with light rays and bubbles."
          },
          {
            "names": [
              "data-aero-photo"
            ],
            "on": "body or one tk-taped",
            "values": [
              "coverflow",
              "tint",
              "plain"
            ],
            "valuesText": null,
            "default": "coverflow",
            "does": "Pictures on an iTunes Cover Flow stage with a reflection (colours untouched), with the 2006 blue tint, or in a plain white frame."
          }
        ],
        "notes": "Built-in style Aero (kit 1.8.0): link /kit/1.8.0/styles/aero.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"aero\"; the style head does this.\n\nThe sheet becomes a desktop. Choose data-aero-os on body: vista (glass, default), xp (Luna blue, green start button, Bliss) or aqua (Mac OS X pinstripes, traffic lights, dock and menu bar). data-aero-wallpaper (aurora, bliss, bubbles, aqua, harmony) defaults to the OS's own. data-aero-sky on body or tk-hero picks the hero scene: day for good news and launches, sunset for wrap-ups and retrospectives, night for overnight or calm reports, ocean for water, travel or anything playful.\n\nEvery tk-section is a window: in vista its h2 is a large blue heading inside; in xp and aqua the h2 is the window title, so keep h2s short (under about 30 characters). The hero is a window onto the sky: keep the title to two lines (about 40 characters); it is mirrored underneath. One tk-stamp becomes a glossy seal (approved green, draft amber, rejected red).\n\nLead the body with tk-stats (four dark gadgets with reflections) and tk-grid of tk-card (each card a small window titled by its h3). Decision callouts carry a green shield; warnings are IE's yellow bar; risk red; tip green. Tables are an Explorer list; tk-code a Notepad window; tk-details and tk-accordion the XP task pane; tk-slip a yellow balloon tip; tk-taped pictures sit on a Cover Flow stage (data-aero-photo=tint or plain to change); the reply is an MSN window. tk-torn is a taskbar strip with its label; tk-progress lives in the taskbar.\n\nSuits product updates, launches, onboarding, release notes, retro and playful reports. Too sunny for incidents, losses or sensitive news. Avoid cards inside cards and very long titles.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-aero-os (body): vista | xp | aqua; default vista. Window chrome, title bars, taskbar or dock and menu bar, and the default wallpaper.\n- data-aero-wallpaper (body): aurora | bliss | bubbles | aqua | harmony; default follows the OS (vista aurora, xp bliss, aqua aqua). The desktop behind the windows.\n- data-aero-sky (body or tk-hero): day | sunset | night | ocean; default day. The hero window's scene: blue sky and green hills; a dusk sky with purple hills; stars, a moon and dark hills; underwater with light rays and bubbles.\n- data-aero-photo (body or one tk-taped): coverflow | tint | plain; default coverflow. Pictures on an iTunes Cover Flow stage with a reflection (colours untouched), with the 2006 blue tint, or in a plain white frame."
      },
      {
        "key": "broadsheet",
        "name": "Broadsheet",
        "description": "A newspaper front page: a nameplate, a banner headline, ruled columns, drop caps and pull quotes.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/broadsheet.css",
        "swatches": [
          "#efe8d6",
          "#191713",
          "#9b1c1c",
          "#e4dac2"
        ],
        "knobs": [
          {
            "names": [
              "--bs-nameplate"
            ],
            "on": "body, quoted",
            "values": null,
            "valuesText": "the paper's name",
            "default": "\"The Tacksheet Tribune\"",
            "does": ""
          },
          {
            "names": [
              "--bs-nameplate-size"
            ],
            "on": "",
            "values": null,
            "valuesText": "0.6-1.2; lower it for long names",
            "default": "1",
            "does": ""
          },
          {
            "names": [
              "--bs-ear-left",
              "--bs-ear-right",
              "--bs-weather",
              "--bs-vol",
              "--bs-price"
            ],
            "on": "body, quoted",
            "values": null,
            "valuesText": "short text",
            "default": "the Tribune's",
            "does": ""
          },
          {
            "names": [
              "data-nameplate"
            ],
            "on": "body",
            "values": [
              "blackletter",
              "modern"
            ],
            "valuesText": null,
            "default": "blackletter",
            "does": ""
          },
          {
            "names": [
              "data-columns"
            ],
            "on": "body or tk-section",
            "values": [
              "1",
              "2",
              "3",
              "4"
            ],
            "valuesText": null,
            "default": "3",
            "does": ""
          },
          {
            "names": [
              "data-paper"
            ],
            "on": "body",
            "values": [
              "newsprint",
              "white",
              "salmon"
            ],
            "valuesText": null,
            "default": "newsprint",
            "does": ""
          },
          {
            "names": [
              "data-photo"
            ],
            "on": "body or tk-taped/tk-image",
            "values": [
              "natural",
              "halftone"
            ],
            "valuesText": null,
            "default": "natural",
            "does": ""
          }
        ],
        "notes": "Built-in style Broadsheet (kit 1.8.0): link /kit/1.8.0/styles/broadsheet.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"broadsheet\"; the style head does this.\n\nCompose like a front page. The title is the banner headline (5 to 10 words); the dek is the deck under it; the byline becomes the dateline. A hero tk-stamp becomes the edition ear (one short word).\n\nMake the masthead fit the content: set --bs-nameplate to the publication's name (\"The Harbor Ledger\"), --bs-ear-left to its motto, --bs-ear-right to the edition, --bs-weather to a one-line ear when there is no stamp, --bs-vol and --bs-price for the dateline bar. Use data-nameplate=modern for business, tech or civic subjects and blackletter for tradition; lower --bs-nameplate-size for names over about 20 characters. data-paper=salmon reads as a financial paper, white as a modern daily.\n\nA section of three or more paragraphs of plain prose is a story set in columns (three by default; data-columns on body or one tk-section: 1 for an editorial, 2 for features, 4 for briefs). A section that holds lists, tables, charts, stats, callouts, code or a form reads as one column. The first section opens with a drop cap.\n\nLead with: tk-stats (a boxed figures panel), tk-table (a listings table; numbers right-aligned), tk-grid of tk-card with charts, tk-slip with from= (a reader's letter), blockquote (a pull quote), one decision callout. Pictures keep their own colours; set data-photo=halftone on body or on one tk-taped/tk-image to print a photo in grayscale through a dot screen (never for screenshots or charts, where colour carries meaning).\n\nAvoid: long code, many callouts, more than one stamp, tk-mark on long phrases (it prints as a yellow highlighter).\n\nKnobs (set them on the elements named; leave one out for its default):\n- --bs-nameplate (body, quoted): the paper's name; default \"The Tacksheet Tribune\"\n- --bs-nameplate-size: 0.6-1.2; lower it for long names; default 1\n- --bs-ear-left / --bs-ear-right / --bs-weather / --bs-vol / --bs-price (body, quoted): short text; default the Tribune's\n- data-nameplate (body): blackletter | modern; default blackletter\n- data-columns (body or tk-section): 1 | 2 | 3 | 4; default 3\n- data-paper (body): newsprint | white | salmon; default newsprint\n- data-photo (body or tk-taped/tk-image): natural | halftone; default natural"
      },
      {
        "key": "brutal",
        "name": "Brutal",
        "description": "Constructivist posters: giant condensed capitals, a visible grid, hard diagonals and raw tables.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/brutal.css",
        "swatches": [
          "#E9E7E0",
          "#0D0D0D",
          "#D41F17",
          "#FFD200"
        ],
        "knobs": [
          {
            "names": [
              "data-br-palette"
            ],
            "on": "body",
            "values": [
              "primaries",
              "mono"
            ],
            "valuesText": null,
            "default": "primaries",
            "does": "primaries = red, blue, yellow and black on newsprint; mono = ink plus one accent (set --br-accent, default #FF4F00; the accent carries dark text so any bright colour works)."
          },
          {
            "names": [
              "--br-a",
              "--br-b",
              "--br-c"
            ],
            "on": "body",
            "values": null,
            "valuesText": null,
            "default": "#D41F17 / #1336B0 / #FFD200",
            "does": "custom palette: a = hot colour that carries light text (title bar, section 1 numeral, chart series 1), b = deep colour for links and blocks, c = bright highlight that carries dark text. Optional --br-paper, --br-ink, --br-on-a, --br-on-c."
          },
          {
            "names": [
              "data-br-hero"
            ],
            "on": "body",
            "values": [
              "wedge",
              "disc",
              "bars",
              "grid",
              "type"
            ],
            "valuesText": null,
            "default": "wedge",
            "does": "the composition in the hero's right panel; type = no artwork, the title takes the full width."
          },
          {
            "names": [
              "data-br-geometry"
            ],
            "on": "body",
            "values": [
              "some",
              "lots",
              "none"
            ],
            "valuesText": null,
            "default": "some",
            "does": "how many shapes appear (hero extras, stat corners, the reply card's red cut). none = pure typographic brutalism."
          },
          {
            "names": [
              "--br-rot",
              "data-br-rotate"
            ],
            "on": "body",
            "values": [
              "0",
              "1",
              "2"
            ],
            "valuesText": null,
            "default": "1",
            "does": "rotation intensity for the title, numerals, stamp band, clipping, print and ticker band. 0 = everything square."
          },
          {
            "names": [
              "data-br-tone"
            ],
            "on": "tk-section",
            "values": [
              "ink",
              "a",
              "c"
            ],
            "valuesText": null,
            "default": "none",
            "does": "prints one section as a solid block (black, hot colour or yellow) to make it stand out."
          },
          {
            "names": [
              "data-br-photo"
            ],
            "on": "body or one tk-taped/tk-image",
            "values": [
              "natural",
              "halftone"
            ],
            "valuesText": null,
            "default": "natural",
            "does": "natural keeps a picture's own colours; halftone prints it in high-contrast black and white through a dot screen."
          }
        ],
        "notes": "Built-in style Brutal (kit 1.8.0): link /kit/1.8.0/styles/brutal.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"brutal\"; the style head does this.\n\nCompose like a poster, then a broadsheet. Titles: three to eight words; the kit splits them into lines and the second line prints white on the hot bar, so put the phrase you want shouted second (\"Harbor Line: / the autumn / service review\"). Pick the hero with data-br-hero: wedge (default, an argument or a decision), disc (a launch or a single number), bars (movement, schedules, transport), grid (planning, data, structure), type (text-only, serious subjects). Set data-br-palette=\"mono\" with --br-accent for brands with one colour, or set --br-a/--br-b/--br-c to the brand's own primaries; keep --br-a dark enough for white text (4.5:1).\n\nLead each section with a short h2 (one to four words): it sets very wide. The first paragraph after the first h2 sets large, so make it the summary. tk-stats: four short values read best (condensed numerals); labels two to four words. Use tk-grid of tk-card with an h3 first; charts print red, black, blue. One decision callout per sheet (the black arrow block); warnings get the hazard strip. tk-slip is a newspaper clipping, best for one quoted remark. Pictures keep their own colours by default. data-br-photo=\"halftone\" on body or on one tk-taped/tk-image prints them as high-contrast halftone in black and white: right for photos and drawings, wrong for screenshots or anything where colour carries meaning. tk-torn becomes a ticker band: give it one word. To shout a section, put data-br-tone=\"ink\" on it (sparingly: one per sheet).\n\nAvoid: long titles over ten words, more than one toned section, rounded or soft imagery, and gentle topics (condolences, care): use data-br-geometry=\"none\" and data-br-rotate=\"0\" if the content needs calm.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-br-palette (body): primaries | mono; default primaries. primaries = red, blue, yellow and black on newsprint; mono = ink plus one accent (set --br-accent, default #FF4F00; the accent carries dark text so any bright colour works).\n- --br-a / --br-b / --br-c (body): ; default #D41F17 / #1336B0 / #FFD200. custom palette: a = hot colour that carries light text (title bar, section 1 numeral, chart series 1), b = deep colour for links and blocks, c = bright highlight that carries dark text. Optional --br-paper, --br-ink, --br-on-a, --br-on-c.\n- data-br-hero (body): wedge | disc | bars | grid | type; default wedge. the composition in the hero's right panel; type = no artwork, the title takes the full width.\n- data-br-geometry (body): some | lots | none; default some. how many shapes appear (hero extras, stat corners, the reply card's red cut). none = pure typographic brutalism.\n- --br-rot / data-br-rotate (body): 0 | 1 | 2; default 1. rotation intensity for the title, numerals, stamp band, clipping, print and ticker band. 0 = everything square.\n- data-br-tone (tk-section): ink | a | c; default none. prints one section as a solid block (black, hot colour or yellow) to make it stand out.\n- data-br-photo (body or one tk-taped/tk-image): natural | halftone; default natural. natural keeps a picture's own colours; halftone prints it in high-contrast black and white through a dot screen."
      },
      {
        "key": "comic",
        "name": "Comic",
        "description": "A comic book: a cover logo, panels in black gutters, Ben-Day dots, caption boxes and speech balloons.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/comic.css",
        "swatches": [
          "#ffdc2e",
          "#111111",
          "#d01f27",
          "#2f8fe0"
        ],
        "knobs": [
          {
            "names": [
              "data-hero"
            ],
            "on": "body or tk-hero",
            "values": [
              "sunburst",
              "speed",
              "focus",
              "skyline",
              "boom",
              "halftone",
              "plain",
              "image"
            ],
            "valuesText": null,
            "default": "sunburst",
            "does": ""
          },
          {
            "names": [
              "--comic-hero-a",
              "--comic-hero-b"
            ],
            "on": "body or tk-hero style",
            "values": null,
            "valuesText": "any two colours; a is the ray/top/dot tone, b the field",
            "default": "per variant (sunburst #1d7fd6/#43a7f2)",
            "does": ""
          },
          {
            "names": [
              "--comic-hero-image",
              "--comic-hero-position"
            ],
            "on": "",
            "values": null,
            "valuesText": "url(...) of the sheet's own picture, used with data-hero=image",
            "default": "none, 50% 50%",
            "does": ""
          },
          {
            "names": [
              "data-panels"
            ],
            "on": "body",
            "values": [
              "cmyk",
              "pulp",
              "noir"
            ],
            "valuesText": null,
            "default": "cmyk",
            "does": ""
          },
          {
            "names": [
              "data-panel"
            ],
            "on": "tk-section",
            "values": [
              "cyan",
              "yellow",
              "magenta",
              "green",
              "paper"
            ],
            "valuesText": null,
            "default": "rotates cyan, yellow, magenta",
            "does": ""
          },
          {
            "names": [
              "data-pattern"
            ],
            "on": "body or tk-section",
            "values": [
              "dots",
              "hatch",
              "speed",
              "burst",
              "plain"
            ],
            "valuesText": null,
            "default": "dots",
            "does": ""
          },
          {
            "names": [
              "data-caption"
            ],
            "on": "body",
            "values": [
              "narration",
              "plain",
              "loud"
            ],
            "valuesText": null,
            "default": "narration",
            "does": ""
          },
          {
            "names": [
              "data-photo"
            ],
            "on": "body or tk-taped/tk-image",
            "values": [
              "natural",
              "pop",
              "mono"
            ],
            "valuesText": null,
            "default": "natural",
            "does": ""
          },
          {
            "names": [
              "--cm-publisher",
              "--cm-issue",
              "--cm-price"
            ],
            "on": "body, quoted strings",
            "values": null,
            "valuesText": "any short text",
            "default": "\"Tacksheet Comics\", \"No. 1\", \"25c\"",
            "does": ""
          }
        ],
        "notes": "Built-in style Comic (kit 1.8.0): link /kit/1.8.0/styles/comic.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"comic\"; the style head does this.\n\nCompose like a comic issue. The title is the cover logo: 3 to 7 words read best. The dek is the cover caption, the byline the credits strip.\n\nPick the cover for the story with data-hero on body (or tk-hero): sunburst (default, announcements), speed (launches, fast change), focus (one big reveal or a problem), skyline (places, cities, night work), boom (incidents, big wins), halftone (calm reports), plain (serious content), image (your own picture: set --comic-hero-image:url(...) on body; the frame, logo and dots stay). Recolour any cover with --comic-hero-a and --comic-hero-b (e.g. greens for growth, reds for alerts). Invent within the frame: two colours plus a variant cover nearly every subject.\n\nPanels: data-panels on body picks the scheme: cmyk (bright default), pulp (aged newsprint, for history and retrospectives), noir (black and white with a red spot, for incidents and investigations). data-panel on a tk-section fixes one panel's ink (cyan, yellow, magenta, green, paper); data-pattern on body or a section swaps the texture (dots, hatch, speed, burst, plain). Use a different texture for the section that matters most.\n\nTone: data-caption=narration (default: 'Meanwhile...', 'Meanwhile, in the python lab...', POW!), plain (no jokes; use it for anything sensitive or customer-facing), loud (red shouting captions, for launches). Photos keep their own colours by default (natural), so screenshots and product shots read true. Set data-photo=pop on body or on one tk-taped/tk-image to print them in the panel scheme (a cyan duotone through magenta dots in cmyk, sepia in pulp, black and white in noir), or mono for plain black and white.\n\nEvery tk-section is a panel with its h2 in a yellow caption box and a numbered disc beside it; keep headings short. Anything written straight into the page between sections gets a plain panel so it never sits in the black gutter, but sections read better. Keep paragraphs to two to four sentences: Comic Neue tires in long runs.\n\nLead with tk-stats (tilted mini panels), tk-callout (speech balloons, one or two sentences), tk-slip with from= (a balloon with the speaker tag), blockquote and a captioned picture. Charts use flat comic colours; keep series few. tk-torn text should be one or two words.\n\nAvoid long tables and long code, tk-mark on long phrases, and more than one stamp. Rename the imprint with --cm-publisher, --cm-issue and --cm-price (quoted strings) on body.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-hero (body or tk-hero): sunburst | speed | focus | skyline | boom | halftone | plain | image; default sunburst\n- --comic-hero-a / --comic-hero-b (body or tk-hero style): any two colours; a is the ray/top/dot tone, b the field; default per variant (sunburst #1d7fd6/#43a7f2)\n- --comic-hero-image / --comic-hero-position: url(...) of the sheet's own picture, used with data-hero=image; default none, 50% 50%\n- data-panels (body): cmyk | pulp | noir; default cmyk\n- data-panel (tk-section): cyan | yellow | magenta | green | paper; default rotates cyan, yellow, magenta\n- data-pattern (body or tk-section): dots | hatch | speed | burst | plain; default dots\n- data-caption (body): narration | plain | loud; default narration\n- data-photo (body or tk-taped/tk-image): natural | pop | mono; default natural\n- --cm-publisher / --cm-issue / --cm-price (body, quoted strings): any short text; default \"Tacksheet Comics\", \"No. 1\", \"25c\""
      },
      {
        "key": "gallery",
        "name": "Gallery",
        "description": "An exhibition: a lit title wall, quiet white rooms, light serif titles and wall labels.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/gallery.css",
        "swatches": [
          "#FFFFFF",
          "#E9E6DF",
          "#1A1A1A",
          "#B3001B"
        ],
        "knobs": [
          {
            "names": [
              "data-title"
            ],
            "on": "body",
            "values": [
              "quiet",
              "bold",
              "monumental"
            ],
            "valuesText": null,
            "default": "bold",
            "does": ""
          },
          {
            "names": [
              "data-wall"
            ],
            "on": "body",
            "values": [
              "warm",
              "white",
              "grey",
              "sage",
              "slate",
              "oxblood",
              "ink"
            ],
            "valuesText": null,
            "default": "warm",
            "does": ""
          },
          {
            "names": [
              "--gallery-wall",
              "--gallery-wall-ink"
            ],
            "on": "body style",
            "values": null,
            "valuesText": "any wall colour and a text colour with 4.5:1 on it",
            "default": "#EFECE6, #1A1A1A",
            "does": ""
          },
          {
            "names": [
              "data-spotlight"
            ],
            "on": "body",
            "values": [
              "on",
              "off"
            ],
            "valuesText": null,
            "default": "on",
            "does": ""
          },
          {
            "names": [
              "data-space"
            ],
            "on": "body",
            "values": [
              "vast",
              "compact"
            ],
            "valuesText": null,
            "default": "vast",
            "does": ""
          },
          {
            "names": [
              "data-feature"
            ],
            "on": "tk-section, tk-callout, tk-stats, tk-taped, tk-image, tk-card",
            "values": null,
            "valuesText": "present on one or two elements per sheet",
            "default": "absent",
            "does": ""
          },
          {
            "names": [
              "--gallery-dot"
            ],
            "on": "body style",
            "values": null,
            "valuesText": "the red-dot colour",
            "default": "#B3001B",
            "does": ""
          }
        ],
        "notes": "Built-in style Gallery (kit 1.8.0): link /kit/1.8.0/styles/gallery.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"gallery\"; the style head does this.\n\nHang the sheet as an exhibition. The title wall is the hero: keep the title to 3 to 7 words (it sets in light Cormorant at display size); the dek sits as wall text beside it.\n\nMake things stand out with space and scale, not labels. Put data-feature on the one or two things a visitor must stop at: a tk-section (its wall colour runs edge to edge with a spotlight and its title grows), a tk-callout (huge wall text standing alone: one sentence), tk-stats (a row of large numerals), a tk-taped/tk-image or tk-card (the work alone on a lit wall). Use it sparingly; emphasis comes from what is left quiet.\n\nPick the wall for the subject with data-wall on body: warm (default), white, grey, sage (nature, health), slate (technical, evening), oxblood (culture, finance, drama), ink (launches, night). Or set --gallery-wall and --gallery-wall-ink. Dark walls re-ink featured rooms; their charts hang as light prints. data-title=quiet for long sheets with many rooms, monumental for short ones with one big idea. data-spotlight=off flattens the light; data-space=compact halves the gaps for long sheets.\n\nLead with one strong picture (tk-taped or tk-image with a caption), short paragraphs, a decision callout (red dot), tk-stats (a colophon of three to six figures; feature it when the numbers are the story) and tk-table (a checklist with italic titles). Keep charts to one or two series.\n\nAvoid: labels above titles (the style has none; do not add them in text), stacks of callouts, long code, more than one stamp, tk-mark on long phrases.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-title (body): quiet | bold | monumental; default bold\n- data-wall (body): warm | white | grey | sage | slate | oxblood | ink; default warm\n- --gallery-wall / --gallery-wall-ink (body style): any wall colour and a text colour with 4.5:1 on it; default #EFECE6, #1A1A1A\n- data-spotlight (body): on | off; default on\n- data-space (body): vast | compact; default vast\n- data-feature (tk-section, tk-callout, tk-stats, tk-taped, tk-image, tk-card): present on one or two elements per sheet; default absent\n- --gallery-dot (body style): the red-dot colour; default #B3001B"
      },
      {
        "key": "geocities",
        "name": "GeoCities",
        "description": "A 1997 home page: tiled wallpaper, rainbow WordArt, marquees, hit counters and a guestbook.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/geocities.css",
        "swatches": [
          "#000014",
          "#ffff00",
          "#0000ee",
          "#c0c0c0"
        ],
        "knobs": [
          {
            "names": [
              "data-tile"
            ],
            "on": "body",
            "values": [
              "stars",
              "clouds",
              "hearts",
              "matrix",
              "grid"
            ],
            "valuesText": null,
            "default": "stars",
            "does": ""
          },
          {
            "names": [
              "--gc-tile",
              "--gc-sky",
              "--gc-tile-size",
              "--gc-hero-ink"
            ],
            "on": "body style",
            "values": null,
            "valuesText": "your own tile url(...), the colour under it, its size, and the hero text colour on it",
            "default": "the starfield",
            "does": ""
          },
          {
            "names": [
              "data-banner"
            ],
            "on": "body",
            "values": [
              "on",
              "off"
            ],
            "valuesText": null,
            "default": "on",
            "does": ""
          },
          {
            "names": [
              "--gc-banner"
            ],
            "on": "body, quoted",
            "values": null,
            "valuesText": "any short text",
            "default": "\"★☆★ Welcome to my Home Page! ★☆★\"",
            "does": ""
          },
          {
            "names": [
              "data-palette"
            ],
            "on": "body",
            "values": [
              "classic",
              "pastel",
              "ocean",
              "sunset"
            ],
            "valuesText": null,
            "default": "classic",
            "does": ""
          },
          {
            "names": [
              "data-marquee"
            ],
            "on": "body",
            "values": [
              "on"
            ],
            "valuesText": null,
            "default": "off",
            "does": ""
          },
          {
            "names": [
              "data-photo"
            ],
            "on": "body or tk-taped",
            "values": [
              "natural",
              "gif"
            ],
            "valuesText": null,
            "default": "natural",
            "does": ""
          }
        ],
        "notes": "Built-in style GeoCities (kit 1.8.0): link /kit/1.8.0/styles/geocities.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"geocities\"; the style head does this.\n\nCompose like a 1997 personal home page. The title is WordArt (3 to 8 words); the dek is an LED sign; data-marquee=on on body scrolls it across one line (keep it to one sentence then). The welcome banner follows the byline: rewrite it with --gc-banner on body (\"Thanks 4 stopping by!!\") or hide it with data-banner=off.\n\nPick wallpaper for the subject with data-tile on body: stars (default, space and night), clouds (Windows 95 sky, cheerful), hearts (fan pages, people, celebrations), matrix (code, hacking, security), grid (music, games, neon). Or set your own: --gc-tile:url(...), --gc-sky, --gc-tile-size and --gc-hero-ink. Panels recolour with data-palette: classic (default), pastel, ocean, sunset.\n\nEvery section is a ridge-bordered panel with a ball-bullet number and a rainbow rule; the first gets a COOL! badge. Keep headings short. Anything written straight into the page between sections (paragraphs, lists, pictures, charts) gets a plain panel of its own so it never sits on the wallpaper, but sections read better.\n\nLead with tk-stats (hit counters; short values like 184.3k read best), tk-slip with from= (a guestbook entry), tk-table (a 3D table), callouts (decision in a double border, warnings on hazard tape), a picture with a caption, and the reply (Sign my Guestbook!). Pictures keep their own colours, so screenshots read true; data-photo=gif on body or on one tk-taped gives them the 1997 GIF look (warm, oversaturated, dithered, pixelated).\n\nAvoid long paragraphs (Times on pastel is fine for a few sentences), long tables, more than one stamp and tk-mark on long phrases. Animations stop under reduced motion.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-tile (body): stars | clouds | hearts | matrix | grid; default stars\n- --gc-tile / --gc-sky / --gc-tile-size / --gc-hero-ink (body style): your own tile url(...), the colour under it, its size, and the hero text colour on it; default the starfield\n- data-banner (body): on | off; default on\n- --gc-banner (body, quoted): any short text; default \"★☆★ Welcome to my Home Page! ★☆★\"\n- data-palette (body): classic | pastel | ocean | sunset; default classic\n- data-marquee (body): on; default off\n- data-photo (body or tk-taped): natural | gif; default natural"
      },
      {
        "key": "glossy",
        "name": "Glossy",
        "description": "A fashion magazine: a Didone masthead, cover lines, magazine columns and one accent colour.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/glossy.css",
        "swatches": [
          "#fdfcfa",
          "#111010",
          "#c0112d",
          "#c8b17d"
        ],
        "knobs": [
          {
            "names": [
              "data-columns"
            ],
            "on": "body or tk-section",
            "values": [
              "1",
              "2",
              "3"
            ],
            "valuesText": null,
            "default": "2 on screens 1000px and wider",
            "does": ""
          },
          {
            "names": [
              "data-density"
            ],
            "on": "body",
            "values": [
              "airy",
              "balanced",
              "compact"
            ],
            "valuesText": null,
            "default": "balanced",
            "does": ""
          },
          {
            "names": [
              "data-palette"
            ],
            "on": "body",
            "values": [
              "ivory",
              "blush",
              "noir"
            ],
            "valuesText": null,
            "default": "ivory",
            "does": ""
          },
          {
            "names": [
              "--glossy-accent"
            ],
            "on": "body style",
            "values": null,
            "valuesText": "any colour with 4.5:1 on the paper",
            "default": "#c0112d (noir: #ff4d6a)",
            "does": ""
          },
          {
            "names": [
              "--gl-masthead",
              "--gl-issue"
            ],
            "on": "body, quoted strings",
            "values": null,
            "valuesText": "short text; the masthead fills the page width",
            "default": "\"Tacksheet\", \"The Review Issue\"",
            "does": ""
          }
        ],
        "notes": "Built-in style Glossy (kit 1.8.0): link /kit/1.8.0/styles/glossy.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"glossy\"; the style head does this.\n\nCompose like a magazine feature. The title is the cover line: 4 to 8 words, it breaks into roman and italic lines. The dek is the standfirst (one or two sentences); the byline becomes the issue line under it. Set --gl-masthead on body to the subject's name (one word reads best) and --gl-issue to the issue name.\n\nA section of plain running prose flows in two magazine columns on wide screens; a section with lists, charts, tk-stats, tk-table, tk-split, tk-code, callouts or the reply reads as one centred column. Use data-columns=1 on a section for a single long argument, 3 on body for dense notes, and data-density=compact for long sheets or airy for a short, luxurious one. A section holding .tk-controls and a tk-question sets them side by side as a spread.\n\nLead with: a tk-split holding a picture and a short story (it bleeds to the screen edge as a photo spread), tk-stats of three or four figures (Didone numerals, alternating italic accent), a blockquote (a large italic pull quote), callouts of one or two sentences (centred standfirsts between hairlines; the kind shows as the colour of the top rule), and the reply (an RSVP card).\n\nPalette: data-palette=ivory (default), blush (pink stock, softer, for culture and people stories), noir (black stock with ivory type, for launches and evening reads). Set --glossy-accent to a brand colour; keep it dark enough for 4.5:1 on the paper (on noir, a light colour).\n\nAvoid: long tables (they work but read as back-of-book listings), more than one stamp, tk-mark on long phrases (it renders as italic accent text), many short sections (each is a feature with a folio). No labels above headings: the style removed them on purpose.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-columns (body or tk-section): 1 | 2 | 3; default 2 on screens 1000px and wider\n- data-density (body): airy | balanced | compact; default balanced\n- data-palette (body): ivory | blush | noir; default ivory\n- --glossy-accent (body style): any colour with 4.5:1 on the paper; default #c0112d (noir: #ff4d6a)\n- --gl-masthead / --gl-issue (body, quoted strings): short text; the masthead fills the page width; default \"Tacksheet\", \"The Review Issue\""
      },
      {
        "key": "keynote",
        "name": "Keynote",
        "description": "A product keynote: huge headlines over a ribbon of light, black chapters and figures that animate in.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/keynote.css",
        "swatches": [
          "#ffffff",
          "#1d1d1f",
          "#0071e3",
          "#00788f"
        ],
        "knobs": [
          {
            "names": [
              "data-keynote-scheme"
            ],
            "on": "body, tk-hero or tk-section",
            "values": [
              "ocean",
              "sunset",
              "meadow",
              "graphite",
              "spectrum",
              "mono"
            ],
            "valuesText": null,
            "default": "ocean",
            "does": "Gradient and solid ink for the title's last line, first stat, underline marks, heading full stops, steps, timeline dots, slider, hero bloom and chart series 1. ocean: transport, tech, finance; sunset: warm or urgent news; meadow: growth, sustainability, health; graphite: sober or premium; spectrum: celebratory launches; mono: when colour would distract."
          },
          {
            "names": [
              "data-keynote-hero"
            ],
            "on": "body or tk-hero",
            "values": [
              "glow",
              "stage",
              "rings",
              "plain"
            ],
            "valuesText": null,
            "default": "glow",
            "does": "Hero background: glow, a grained silk ribbon of light in the scheme colours (soft body, folded crest, a fainter second band) that sweeps across behind the copy; a black stage under a top light; three concentric scheme rings around the copy; nothing."
          },
          {
            "names": [
              "data-keynote-flow"
            ],
            "on": "body or tk-hero",
            "values": [
              "rise",
              "fall"
            ],
            "valuesText": null,
            "default": "rise",
            "does": "Direction of the glow ribbon. rise: it enters low on the left and leaves off the top right; fall: mirrored, it leaves off the top left. Pick the side that keeps the ribbon away from a long first title line, or alternate between related sheets."
          },
          {
            "names": [
              "data-keynote-chapters"
            ],
            "on": "body",
            "values": [
              "alternate",
              "dark",
              "light"
            ],
            "valuesText": null,
            "default": "alternate",
            "does": "Which chapters are black: every second one, all of them (a Pro look), or none."
          },
          {
            "names": [
              "data-keynote-ground"
            ],
            "on": "one tk-section",
            "values": [
              "dark",
              "light"
            ],
            "valuesText": null,
            "default": "follows data-keynote-chapters",
            "does": "Force one chapter's ground."
          },
          {
            "names": [
              "data-keynote-motion"
            ],
            "on": "body",
            "values": [
              "on",
              "off"
            ],
            "valuesText": null,
            "default": "on",
            "does": "off keeps the designed still even for visitors who allow motion (dense reference sheets)."
          }
        ],
        "notes": "Built-in style Keynote (kit 1.8.0): link /kit/1.8.0/styles/keynote.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"keynote\"; the style head does this.\n\nCompose like a keynote: few words, big type, lots of air. Keep the tk-tape-title to three to eight words and do not end it with punctuation; the last rendered line takes the scheme gradient and the style adds a scheme-coloured full stop; h2s get a short scheme-coloured bar beneath. Put status in one tk-stamp: it becomes a seal badge after the byline, never a label above the title. Choose data-keynote-scheme on body to match the subject (ocean, sunset, meadow, graphite, spectrum, mono) and data-keynote-hero (glow, stage, rings, plain; glow is a silk ribbon whose direction data-keynote-flow=rise|fall sets); a section can carry its own data-keynote-scheme.\n\nChapters alternate white and black by default (data-keynote-chapters=alternate|dark|light on body; data-keynote-ground=dark|light on one tk-section). Charts always sit on light tiles. The paragraph right after each h2 is a grey lede that fills with ink as it scrolls: make it the one-sentence summary and put the key phrase in tk-mark (full ink); tk-mark kind=underline takes the gradient. Lead sections with tk-stats (two or four short values; the first takes the gradient, all climb into place). Use one decision tk-callout per sheet: it gets large type and a bloom-washed tile; other callouts are tiles led by a symbol. tk-taped images become a floating product shot that tilts up on scroll. tk-slip is an iOS notification. tk-timeline runs horizontally on wide screens (short 'when' values such as 6 Oct or April); tk-steps get big scheme numerals; tk-tabs a segmented pill; tk-details and tk-accordion FAQ rows.\n\nAvoid long headlines, walls of text, several callouts in a row, many-series charts and more than one stamp. Suits launches, quarterly reviews, proposals, pitches and premium updates. Set data-keynote-motion=off on dense reference sheets.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-keynote-scheme (body, tk-hero or tk-section): ocean | sunset | meadow | graphite | spectrum | mono; default ocean. Gradient and solid ink for the title's last line, first stat, underline marks, heading full stops, steps, timeline dots, slider, hero bloom and chart series 1. ocean: transport, tech, finance; sunset: warm or urgent news; meadow: growth, sustainability, health; graphite: sober or premium; spectrum: celebratory launches; mono: when colour would distract.\n- data-keynote-hero (body or tk-hero): glow | stage | rings | plain; default glow. Hero background: glow, a grained silk ribbon of light in the scheme colours (soft body, folded crest, a fainter second band) that sweeps across behind the copy; a black stage under a top light; three concentric scheme rings around the copy; nothing.\n- data-keynote-flow (body or tk-hero): rise | fall; default rise. Direction of the glow ribbon. rise: it enters low on the left and leaves off the top right; fall: mirrored, it leaves off the top left. Pick the side that keeps the ribbon away from a long first title line, or alternate between related sheets.\n- data-keynote-chapters (body): alternate | dark | light; default alternate. Which chapters are black: every second one, all of them (a Pro look), or none.\n- data-keynote-ground (one tk-section): dark | light; default follows data-keynote-chapters. Force one chapter's ground.\n- data-keynote-motion (body): on | off; default on. off keeps the designed still even for visitors who allow motion (dense reference sheets)."
      },
      {
        "key": "launch",
        "name": "Launch",
        "description": "A modern launch page: dark or white, ruled guides, a gradient-lit hero and bento panels.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/launch.css",
        "swatches": [
          "#08090a",
          "#f7f8f8",
          "#9aa8ff",
          "#8a8f98"
        ],
        "knobs": [
          {
            "names": [
              "data-launch-mode"
            ],
            "on": "body",
            "values": [
              "night",
              "day"
            ],
            "valuesText": null,
            "default": "night",
            "does": "Near-black Linear/Vercel stock, or Stripe's white with navy ink and soft card shadows."
          },
          {
            "names": [
              "data-launch-art"
            ],
            "on": "body (hero, divider, slip avatar, glows, accent) or one tk-section (a wash of the art behind its top)",
            "values": [
              "horizon",
              "chroma",
              "current",
              "arc",
              "eclipse",
              "club",
              "prism",
              "nebula",
              "ribbon",
              "silk",
              "none"
            ],
            "valuesText": null,
            "default": "horizon (night), ribbon (day)",
            "does": "Which approved look lights the page; also sets the accent, glow colours and chart series 1-2. horizon, arc: calm and technical; chroma, current: fresh green-blue; eclipse, club, nebula: warm or playful; prism: bold; ribbon, silk: bright day pages; none: a pure black page."
          },
          {
            "names": [
              "data-launch-head"
            ],
            "on": "body",
            "values": [
              "sans",
              "serif"
            ],
            "valuesText": null,
            "default": "sans",
            "does": "Geist headings, or Instrument Serif for editorial, human or announcement-led sheets."
          },
          {
            "names": [
              "--launch-accent"
            ],
            "on": "body (style attribute)",
            "values": null,
            "valuesText": "any colour with 4.5:1 on the ground",
            "default": "from the art",
            "does": "Overrides links, focus rings, selection, toggles and slider fill."
          }
        ],
        "notes": "Built-in style Launch (kit 1.8.0): link /kit/1.8.0/styles/launch.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"launch\"; the style head does this.\n\nCompose like a Linear or Stripe launch page. Pick data-launch-art on body to match the subject (horizon or arc: calm, technical; chroma or current: fresh, green-blue; eclipse, club, nebula: warm or playful; prism: bold; none: pure black). data-launch-mode=day gives Stripe's white page (art ribbon or silk); data-launch-head=serif suits editorial or human stories. Keep the tk-tape-title short (under about 45 characters): it is left-aligned and large. Put status in one tk-stamp: a chip with a live dot beside the byline.\n\nOn wide screens each section is heading-left, reading-right: write the paragraph straight after the h2 as the lede (only that one is) and put its key phrases in tk-mark or strong (they print in full ink against grey). A tk-slip placed between the h2 and that paragraph sits under the heading as a thread message. Stats, grids, tables, code, controls and replies span the full width below.\n\ntk-stats: three to five short values, printed as light numerals in a ruled strip. Put charts in tk-grid tk-card panels (series 1 and 2 take the art colours; prefer one or two series). Use one decision tk-callout: its edge lights up and travels; warning and risk tint the panel. Tables read as issue lists. tk-code is an editor panel; tk-taped images keep their colours in a bezel with light beneath, so use screenshots or diagrams. blockquote prints as a large serif quote. tk-timeline is a changelog (short dates), tk-steps keycap numbers, tk-tabs a segmented bar. To spotlight one section, add data-launch-art=<art> to it.\n\nAvoid centred long titles, walls of text, more than one stamp and light photographs on the night page. Suits launches, release notes, changelogs, product updates, demos and engineering reports.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-launch-mode (body): night | day; default night. Near-black Linear/Vercel stock, or Stripe's white with navy ink and soft card shadows.\n- data-launch-art (body (hero, divider, slip avatar, glows, accent) or one tk-section (a wash of the art behind its top)): horizon | chroma | current | arc | eclipse | club | prism | nebula | ribbon | silk | none; default horizon (night), ribbon (day). Which approved look lights the page; also sets the accent, glow colours and chart series 1-2. horizon, arc: calm and technical; chroma, current: fresh green-blue; eclipse, club, nebula: warm or playful; prism: bold; ribbon, silk: bright day pages; none: a pure black page.\n- data-launch-head (body): sans | serif; default sans. Geist headings, or Instrument Serif for editorial, human or announcement-led sheets.\n- --launch-accent (body (style attribute)): any colour with 4.5:1 on the ground; default from the art. Overrides links, focus rings, selection, toggles and slider fill."
      },
      {
        "key": "magazine",
        "name": "Magazine",
        "description": "A magazine feature: a cover spread, justified columns, drop caps, pull quotes and sidebars.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/magazine.css",
        "swatches": [
          "#FBF8F2",
          "#1B1A17",
          "#DB8A5C",
          "#7F3624"
        ],
        "knobs": [
          {
            "names": [
              "data-mg-palette"
            ],
            "on": "body, or any tk-section / tk-card / tk-hero / tk-image",
            "values": [
              "dune",
              "lagoon",
              "rose",
              "moss",
              "lapis",
              "dusk",
              "ember",
              "mono"
            ],
            "valuesText": null,
            "default": "dune",
            "does": "the plate colours, drop cap, end mark, marks and chart accent. Match it to the content: lagoon for water and travel, moss for nature and growth, lapis for finance and tech, ember for night and drama, mono for sober topics."
          },
          {
            "names": [
              "--mg-ground",
              "--mg-1",
              "--mg-2",
              "--mg-3",
              "--mg-4"
            ],
            "on": "same",
            "values": null,
            "valuesText": null,
            "default": "#F2E5D9 / #F7CDAA / #DB8A5C / #7F3624 / #FCE6C4",
            "does": "custom palette: ground, light, mid, deep, glow. Take them from the sheet's own photos or brand; keep --mg-3 dark (it colours small labels)."
          },
          {
            "names": [
              "data-mg-shape"
            ],
            "on": "body (hero), or a tk-section / tk-card / tk-hero / tk-image",
            "values": [
              "sun",
              "horizon",
              "bloom",
              "ribbon",
              "eclipse"
            ],
            "valuesText": null,
            "default": "sun on the hero; sections cycle bloom, horizon, eclipse, ribbon; cards cycle horizon, ribbon, sun",
            "does": "the composition of a plate."
          },
          {
            "names": [
              "--mg-x",
              "--mg-y"
            ],
            "on": "same",
            "values": null,
            "valuesText": null,
            "default": "per shape (e.g. 64% 50%)",
            "does": "where the light (sun, glow, eclipse) sits, so the art leans away from nearby text or toward a subject."
          },
          {
            "names": [
              "data-mg-grain"
            ],
            "on": "body",
            "values": [
              "heavy",
              "soft",
              "none"
            ],
            "valuesText": null,
            "default": "heavy",
            "does": "grain on the plates."
          },
          {
            "names": [
              "data-mg-spot"
            ],
            "on": "body or a tk-section",
            "values": [
              "off"
            ],
            "valuesText": null,
            "default": "on",
            "does": "removes the miniature plate beside section headings."
          },
          {
            "names": [
              "data-mg-cover"
            ],
            "on": "body or tk-hero",
            "values": [
              "spread",
              "stack"
            ],
            "valuesText": null,
            "default": "spread",
            "does": "spread: the picture or plate takes the left page and the headline sits on the right (phones stack it, set left). stack: the plate above a centred headline, for very short sheets or titles longer than about twelve words."
          },
          {
            "names": [
              "data-mg-columns"
            ],
            "on": "body or tk-section",
            "values": [
              "auto",
              "1",
              "2",
              "3"
            ],
            "valuesText": null,
            "default": "auto",
            "does": "text columns on wide screens (phones always read in one), only ever for plain running prose: paragraphs, pull quotes, postcards and taped pictures, never lists, tables, charts, forms or callouts. auto: two columns for three to six paragraphs, one long-read column with marginal pull quotes for seven or more. Use 1 for argument-heavy prose, 3 for a long light run."
          },
          {
            "names": [
              "data-mg-opener"
            ],
            "on": "tk-section",
            "values": [
              "plate"
            ],
            "valuesText": null,
            "default": "spot art beside the title",
            "does": "a wide plate opens the department, like a part-title page. Use it on one or two major sections, with data-mg-shape to vary the picture."
          },
          {
            "names": [
              "data-mg-paper"
            ],
            "on": "tk-section",
            "values": [
              "tint"
            ],
            "valuesText": null,
            "default": "plain paper",
            "does": "prints the section on a tinted stock in the palette, edge to edge, to mark a sidebar section, an interview or a letters page."
          },
          {
            "names": [
              "data-mg-numbers"
            ],
            "on": "body or tk-stats",
            "values": [
              "giant",
              "row"
            ],
            "valuesText": null,
            "default": "giant",
            "does": "giant: the first tk-stat as an oversized number beside a ruled list of the rest (lead with the figure that matters). row: an even ruled row of figures for peers of equal weight."
          },
          {
            "names": [
              "data-mg-foot"
            ],
            "on": "tk-section",
            "values": [
              "a short word"
            ],
            "valuesText": null,
            "default": "none (a plain hairline)",
            "does": "printed in the section’s running foot (\"docks\", \"crews\")."
          }
        ],
        "notes": "Built-in style Magazine (kit 1.8.0): link /kit/1.8.0/styles/magazine.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"magazine\"; the style head does this.\n\nWrite it like a magazine feature. Title four to ten words; the dek one or two sentences, set as a roman standfirst; byline names the team and date. Put the best picture in tk-hero (a tk-image or img): it fills the left page of the cover spread. With no picture the cover gets a mesh-gradient plate: choose data-mg-palette from the subject (lagoon water/travel, moss nature, lapis finance/tech, rose people/culture, dusk evening, ember night/drama, mono sober, dune default) and data-mg-shape (sun, horizon, bloom, ribbon, eclipse).\n\nSections are departments: short h2 titles (one to four words); data-mg-foot=\"docks\" on a tk-section prints that word in its running foot. The paragraph after each h2 opens with a drop cap and a small-caps first line, so start it with a word, not a number. A section of three to six paragraphs of plain prose flows into two justified columns on wide screens; sections with lists, tables, charts, forms or callouts read as one column, and seven or more paragraphs read as one long column with the pull quote in the margin. data-mg-columns sets it. One blockquote per section becomes the pull quote; a p.tk-note right after it is the attribution. Put a tk-slip near the top of a section: it becomes a postcard in the right rail. Note/warning/tip callouts are boxed sidebars: keep them to two sentences. Use one decision callout per sheet (the editor's note).\n\nLead a data section with tk-stats: put the headline figure first (it is set oversized). A tk-split with a picture first becomes a spread (the picture bleeds to the page edge). A tk-image on its own is full bleed; give it caption= and credit=. tk-grid cols=\"3\" of cards with images is a picture spread; cards without pictures get plates. Add <tk-toc title=\"In this issue\"> after the hero on sheets with four or more sections. Vary pacing: data-mg-opener=\"plate\" on one major section, data-mg-paper=\"tint\" on a sidebar or letters section.\n\nAvoid: one-paragraph sections everywhere (no columns to read), many callouts, tables wider than six columns, screenshots as the cover.\n\nKnobs (set them on the elements named; leave one out for its default):\n- data-mg-palette (body, or any tk-section / tk-card / tk-hero / tk-image): dune | lagoon | rose | moss | lapis | dusk | ember | mono; default dune. the plate colours, drop cap, end mark, marks and chart accent. Match it to the content: lagoon for water and travel, moss for nature and growth, lapis for finance and tech, ember for night and drama, mono for sober topics.\n- --mg-ground / --mg-1 / --mg-2 / --mg-3 / --mg-4 (same): ; default #F2E5D9 / #F7CDAA / #DB8A5C / #7F3624 / #FCE6C4. custom palette: ground, light, mid, deep, glow. Take them from the sheet's own photos or brand; keep --mg-3 dark (it colours small labels).\n- data-mg-shape (body (hero), or a tk-section / tk-card / tk-hero / tk-image): sun | horizon | bloom | ribbon | eclipse; default sun on the hero; sections cycle bloom, horizon, eclipse, ribbon; cards cycle horizon, ribbon, sun. the composition of a plate.\n- --mg-x / --mg-y (same): ; default per shape (e.g. 64% 50%). where the light (sun, glow, eclipse) sits, so the art leans away from nearby text or toward a subject.\n- data-mg-grain (body): heavy | soft | none; default heavy. grain on the plates.\n- data-mg-spot (body or a tk-section): off; default on. removes the miniature plate beside section headings.\n- data-mg-cover (body or tk-hero): spread | stack; default spread. spread: the picture or plate takes the left page and the headline sits on the right (phones stack it, set left). stack: the plate above a centred headline, for very short sheets or titles longer than about twelve words.\n- data-mg-columns (body or tk-section): auto | 1 | 2 | 3; default auto. text columns on wide screens (phones always read in one), only ever for plain running prose: paragraphs, pull quotes, postcards and taped pictures, never lists, tables, charts, forms or callouts. auto: two columns for three to six paragraphs, one long-read column with marginal pull quotes for seven or more. Use 1 for argument-heavy prose, 3 for a long light run.\n- data-mg-opener (tk-section): plate; default spot art beside the title. a wide plate opens the department, like a part-title page. Use it on one or two major sections, with data-mg-shape to vary the picture.\n- data-mg-paper (tk-section): tint; default plain paper. prints the section on a tinted stock in the palette, edge to edge, to mark a sidebar section, an interview or a letters page.\n- data-mg-numbers (body or tk-stats): giant | row; default giant. giant: the first tk-stat as an oversized number beside a ruled list of the rest (lead with the figure that matters). row: an even ruled row of figures for peers of equal weight.\n- data-mg-foot (tk-section): a short word; default none (a plain hairline). printed in the section’s running foot (\"docks\", \"crews\")."
      },
      {
        "key": "studio",
        "name": "Studio",
        "description": "A design studio's case study: screen-wide titles, numbered sections and a bento of metrics.",
        "base": "none",
        "since": "1.6.0",
        "css": "/kit/1.8.0/styles/studio.css",
        "swatches": [
          "#F1EFEA",
          "#0E0E0E",
          "#D7FF00",
          "#5C5B57"
        ],
        "knobs": [
          {
            "names": [
              "--studio-accent"
            ],
            "on": "body style",
            "values": null,
            "valuesText": "any colour",
            "default": "#D7FF00",
            "does": ""
          },
          {
            "names": [
              "--studio-accent-ink",
              "--studio-accent-on-dark"
            ],
            "on": "",
            "values": null,
            "valuesText": "text colour on the accent; accent shade used as text on ink",
            "default": "#0E0E0E, the accent",
            "does": ""
          },
          {
            "names": [
              "data-accent"
            ],
            "on": "body",
            "values": [
              "acid",
              "orange",
              "pink",
              "mint",
              "cobalt"
            ],
            "valuesText": null,
            "default": "acid",
            "does": ""
          },
          {
            "names": [
              "data-density"
            ],
            "on": "body",
            "values": [
              "tight",
              "regular",
              "spacious"
            ],
            "valuesText": null,
            "default": "regular",
            "does": ""
          },
          {
            "names": [
              "data-grid"
            ],
            "on": "body",
            "values": [
              "on",
              "off"
            ],
            "valuesText": null,
            "default": "on",
            "does": ""
          },
          {
            "names": [
              "data-title"
            ],
            "on": "body",
            "values": [
              "screen",
              "large"
            ],
            "valuesText": null,
            "default": "screen",
            "does": ""
          }
        ],
        "notes": "Built-in style Studio (kit 1.8.0): link /kit/1.8.0/styles/studio.css after the kit stylesheet and keep body data-tk-theme=\"none\" data-tk-style=\"studio\"; the style head does this.\n\nCompose like a studio case study. The title fills the screen: 3 to 6 words, alternate lines turn serif italic; use data-title=large for longer titles. The dek reads after '(About)'; the byline becomes the meta bar under the hero.\n\nEvery section gets a giant sticky (01) in the left columns; keep h2s short (one to three words). A tk-slip sits at the top right of its section as a dark chat bubble.\n\nLead with tk-stats: three to five figures make a bento (the first is a large accent tile, the second an ink tile); put the headline number first. tk-grid of tk-card with charts reads as project tiles. Use one decision callout (accent block with an arrow) and at most one warning (ink with a hazard edge). A picture (tk-taped/tk-image with a caption) becomes a figure with a (Fig.) caption; tk-table a work index; tk-code a window; blockquote a big italic quote with an accent disc.\n\nAccent: pick it for the client or subject with data-accent (acid default, orange, pink, mint, cobalt) or --studio-accent on body. For a dark accent also set --studio-accent-ink:#fff and a light --studio-accent-on-dark. Density: data-density=tight for long sheets, spacious for short launch pages; data-grid=off hides the column lines over busy media.\n\nAvoid: long paragraphs (keep three to five sentences), many callouts, more than one stamp, tk-mark on long phrases (it fills with the accent).\n\nKnobs (set them on the elements named; leave one out for its default):\n- --studio-accent (body style): any colour; default #D7FF00\n- --studio-accent-ink / --studio-accent-on-dark: text colour on the accent; accent shade used as text on ink; default #0E0E0E, the accent\n- data-accent (body): acid | orange | pink | mint | cobalt; default acid\n- data-density (body): tight | regular | spacious; default regular\n- data-grid (body): on | off; default on\n- data-title (body): screen | large; default screen"
      }
    ]
  }
}
