enya-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (389) hide show
  1. package/.env.example +20 -0
  2. package/.github/workflows/ci.yml +70 -0
  3. package/.github/workflows/publish.yml +250 -0
  4. package/.gitmodules +3 -0
  5. package/Cargo.lock +3584 -0
  6. package/Cargo.toml +97 -0
  7. package/crates/enact/Cargo.toml +27 -0
  8. package/crates/enact/src/lib.rs +60 -0
  9. package/crates/enact-a2a/Cargo.toml +25 -0
  10. package/crates/enact-a2a/src/lib.rs +411 -0
  11. package/crates/enact-channels/Cargo.toml +64 -0
  12. package/crates/enact-channels/examples/README.md +80 -0
  13. package/crates/enact-channels/examples/channel_bot.rs +169 -0
  14. package/crates/enact-channels/examples/telegram-echo.rs +34 -0
  15. package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
  16. package/crates/enact-channels/src/config.rs +213 -0
  17. package/crates/enact-channels/src/lib.rs +25 -0
  18. package/crates/enact-channels/src/runtime.rs +237 -0
  19. package/crates/enact-channels/src/security/mod.rs +5 -0
  20. package/crates/enact-channels/src/security/pairing.rs +205 -0
  21. package/crates/enact-channels/src/teams.rs +601 -0
  22. package/crates/enact-channels/src/telegram.rs +2833 -0
  23. package/crates/enact-channels/src/traits.rs +200 -0
  24. package/crates/enact-channels/src/webhook.rs +262 -0
  25. package/crates/enact-channels/src/whatsapp.rs +310 -0
  26. package/crates/enact-cli/Cargo.toml +40 -0
  27. package/crates/enact-cli/src/commands/doctor.rs +62 -0
  28. package/crates/enact-cli/src/commands/mod.rs +3 -0
  29. package/crates/enact-cli/src/commands/run.rs +69 -0
  30. package/crates/enact-cli/src/commands/serve.rs +81 -0
  31. package/crates/enact-cli/src/config.rs +2 -0
  32. package/crates/enact-cli/src/main.rs +79 -0
  33. package/crates/enact-config/Cargo.toml +36 -0
  34. package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
  35. package/crates/enact-config/QUICK_REFERENCE.md +92 -0
  36. package/crates/enact-config/README.md +107 -0
  37. package/crates/enact-config/TESTING.md +161 -0
  38. package/crates/enact-config/examples/test-env-vars.rs +100 -0
  39. package/crates/enact-config/src/config.rs +399 -0
  40. package/crates/enact-config/src/encrypted_store.rs +211 -0
  41. package/crates/enact-config/src/lib.rs +298 -0
  42. package/crates/enact-config/src/secrets.rs +149 -0
  43. package/crates/enact-config/src/sync.rs +260 -0
  44. package/crates/enact-config/test-env-vars.sh +34 -0
  45. package/crates/enact-config/tests/README.md +99 -0
  46. package/crates/enact-config/tests/config_integration_test.rs +202 -0
  47. package/crates/enact-config/tests/security_test.rs +140 -0
  48. package/crates/enact-context/Cargo.toml +41 -0
  49. package/crates/enact-context/src/budget.rs +314 -0
  50. package/crates/enact-context/src/calibrator.rs +535 -0
  51. package/crates/enact-context/src/compactor.rs +392 -0
  52. package/crates/enact-context/src/condenser.rs +826 -0
  53. package/crates/enact-context/src/lib.rs +94 -0
  54. package/crates/enact-context/src/segment.rs +238 -0
  55. package/crates/enact-context/src/step_context.rs +645 -0
  56. package/crates/enact-context/src/token_counter.rs +148 -0
  57. package/crates/enact-context/src/window.rs +372 -0
  58. package/crates/enact-core/Cargo.toml +42 -0
  59. package/crates/enact-core/README.md +98 -0
  60. package/crates/enact-core/src/background/executor.rs +524 -0
  61. package/crates/enact-core/src/background/mod.rs +48 -0
  62. package/crates/enact-core/src/background/target_binding.rs +390 -0
  63. package/crates/enact-core/src/background/trigger.rs +511 -0
  64. package/crates/enact-core/src/callable/callable.rs +152 -0
  65. package/crates/enact-core/src/callable/composite.rs +817 -0
  66. package/crates/enact-core/src/callable/graph.rs +104 -0
  67. package/crates/enact-core/src/callable/llm.rs +211 -0
  68. package/crates/enact-core/src/callable/mod.rs +64 -0
  69. package/crates/enact-core/src/callable/registry.rs +206 -0
  70. package/crates/enact-core/src/context/execution_context.rs +757 -0
  71. package/crates/enact-core/src/context/invocation.rs +99 -0
  72. package/crates/enact-core/src/context/mod.rs +50 -0
  73. package/crates/enact-core/src/context/tenant.rs +175 -0
  74. package/crates/enact-core/src/context/trace.rs +127 -0
  75. package/crates/enact-core/src/flow/conditional.rs +293 -0
  76. package/crates/enact-core/src/flow/mod.rs +43 -0
  77. package/crates/enact-core/src/flow/parallel.rs +437 -0
  78. package/crates/enact-core/src/flow/repeat.rs +534 -0
  79. package/crates/enact-core/src/flow/sequential.rs +248 -0
  80. package/crates/enact-core/src/graph/checkpoint.rs +79 -0
  81. package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
  82. package/crates/enact-core/src/graph/compiled.rs +189 -0
  83. package/crates/enact-core/src/graph/edge.rs +59 -0
  84. package/crates/enact-core/src/graph/graph_schema.rs +218 -0
  85. package/crates/enact-core/src/graph/loader.rs +155 -0
  86. package/crates/enact-core/src/graph/mod.rs +18 -0
  87. package/crates/enact-core/src/graph/node/function.rs +49 -0
  88. package/crates/enact-core/src/graph/node/mod.rs +48 -0
  89. package/crates/enact-core/src/graph/schema.rs +62 -0
  90. package/crates/enact-core/src/inbox/message.rs +405 -0
  91. package/crates/enact-core/src/inbox/mod.rs +31 -0
  92. package/crates/enact-core/src/inbox/store.rs +355 -0
  93. package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
  94. package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
  95. package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
  96. package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
  97. package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
  98. package/crates/enact-core/src/kernel/error.rs +1200 -0
  99. package/crates/enact-core/src/kernel/event.rs +1394 -0
  100. package/crates/enact-core/src/kernel/execution_model.rs +831 -0
  101. package/crates/enact-core/src/kernel/execution_state.rs +189 -0
  102. package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
  103. package/crates/enact-core/src/kernel/ids.rs +2086 -0
  104. package/crates/enact-core/src/kernel/interrupt.rs +125 -0
  105. package/crates/enact-core/src/kernel/kernel.rs +1283 -0
  106. package/crates/enact-core/src/kernel/mod.rs +205 -0
  107. package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
  108. package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
  109. package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
  110. package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
  111. package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
  112. package/crates/enact-core/src/kernel/reducer.rs +808 -0
  113. package/crates/enact-core/src/kernel/replay.rs +153 -0
  114. package/crates/enact-core/src/lib.rs +413 -0
  115. package/crates/enact-core/src/memory/episodic.rs +0 -0
  116. package/crates/enact-core/src/memory/mod.rs +6 -0
  117. package/crates/enact-core/src/memory/semantic.rs +0 -0
  118. package/crates/enact-core/src/memory/trait.rs +0 -0
  119. package/crates/enact-core/src/memory/vector_db.rs +0 -0
  120. package/crates/enact-core/src/memory/working.rs +0 -0
  121. package/crates/enact-core/src/policy/execution_policy.rs +292 -0
  122. package/crates/enact-core/src/policy/filters.rs +458 -0
  123. package/crates/enact-core/src/policy/input_processor.rs +407 -0
  124. package/crates/enact-core/src/policy/long_running.rs +134 -0
  125. package/crates/enact-core/src/policy/mod.rs +193 -0
  126. package/crates/enact-core/src/policy/pii_input.rs +274 -0
  127. package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
  128. package/crates/enact-core/src/policy/tool_policy.rs +407 -0
  129. package/crates/enact-core/src/providers/mod.rs +63 -0
  130. package/crates/enact-core/src/providers/trait.rs +292 -0
  131. package/crates/enact-core/src/runner/callbacks.rs +6 -0
  132. package/crates/enact-core/src/runner/execution_runner.rs +476 -0
  133. package/crates/enact-core/src/runner/loop.rs +117 -0
  134. package/crates/enact-core/src/runner/mod.rs +58 -0
  135. package/crates/enact-core/src/runner/protected_runner.rs +280 -0
  136. package/crates/enact-core/src/signal/inmemory.rs +231 -0
  137. package/crates/enact-core/src/signal/mod.rs +108 -0
  138. package/crates/enact-core/src/streaming/event_logger.rs +195 -0
  139. package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
  140. package/crates/enact-core/src/streaming/mod.rs +108 -0
  141. package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
  142. package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
  143. package/crates/enact-core/src/streaming/protection/context.rs +136 -0
  144. package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
  145. package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
  146. package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
  147. package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
  148. package/crates/enact-core/src/streaming/sse.rs +0 -0
  149. package/crates/enact-core/src/telemetry/exporter.rs +0 -0
  150. package/crates/enact-core/src/telemetry/init.rs +0 -0
  151. package/crates/enact-core/src/telemetry/mod.rs +49 -0
  152. package/crates/enact-core/src/telemetry/spans.rs +245 -0
  153. package/crates/enact-core/src/tool/agent_tool.rs +177 -0
  154. package/crates/enact-core/src/tool/browser/mod.rs +0 -0
  155. package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
  156. package/crates/enact-core/src/tool/cost.rs +247 -0
  157. package/crates/enact-core/src/tool/discovery.rs +0 -0
  158. package/crates/enact-core/src/tool/dispatcher.rs +347 -0
  159. package/crates/enact-core/src/tool/filesystem.rs +231 -0
  160. package/crates/enact-core/src/tool/function.rs +99 -0
  161. package/crates/enact-core/src/tool/git.rs +162 -0
  162. package/crates/enact-core/src/tool/http.rs +214 -0
  163. package/crates/enact-core/src/tool/mcp/client.rs +0 -0
  164. package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
  165. package/crates/enact-core/src/tool/mod.rs +51 -0
  166. package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
  167. package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
  168. package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
  169. package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
  170. package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
  171. package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
  172. package/crates/enact-core/src/tool/shell.rs +147 -0
  173. package/crates/enact-core/src/tool/trait.rs +33 -0
  174. package/crates/enact-core/src/tool/web_search.rs +277 -0
  175. package/crates/enact-core/src/util/config.rs +0 -0
  176. package/crates/enact-core/src/util/errors.rs +0 -0
  177. package/crates/enact-core/src/util/mod.rs +6 -0
  178. package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
  179. package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
  180. package/crates/enact-core/tests/e2e_test.rs +259 -0
  181. package/crates/enact-core/tests/graph_test.rs +130 -0
  182. package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
  183. package/crates/enact-cron/Cargo.toml +28 -0
  184. package/crates/enact-cron/src/lib.rs +44 -0
  185. package/crates/enact-cron/src/schedule.rs +156 -0
  186. package/crates/enact-cron/src/store.rs +589 -0
  187. package/crates/enact-cron/src/types.rs +148 -0
  188. package/crates/enact-gateway/Cargo.toml +31 -0
  189. package/crates/enact-gateway/README.md +30 -0
  190. package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
  191. package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
  192. package/crates/enact-gateway/src/lib.rs +582 -0
  193. package/crates/enact-mcp/Cargo.toml +24 -0
  194. package/crates/enact-mcp/src/lib.rs +178 -0
  195. package/crates/enact-memory/Cargo.toml +25 -0
  196. package/crates/enact-memory/src/backend.rs +20 -0
  197. package/crates/enact-memory/src/chunker.rs +230 -0
  198. package/crates/enact-memory/src/embeddings.rs +221 -0
  199. package/crates/enact-memory/src/lib.rs +67 -0
  200. package/crates/enact-memory/src/markdown.rs +127 -0
  201. package/crates/enact-memory/src/none.rs +61 -0
  202. package/crates/enact-memory/src/sqlite.rs +276 -0
  203. package/crates/enact-memory/src/traits.rs +65 -0
  204. package/crates/enact-memory/src/vector.rs +198 -0
  205. package/crates/enact-oauth/Cargo.toml +27 -0
  206. package/crates/enact-oauth/src/lib.rs +584 -0
  207. package/crates/enact-observability/Cargo.toml +22 -0
  208. package/crates/enact-observability/src/lib.rs +197 -0
  209. package/crates/enact-providers/Cargo.toml +33 -0
  210. package/crates/enact-providers/examples/hello-agent.rs +33 -0
  211. package/crates/enact-providers/src/anthropic.rs +182 -0
  212. package/crates/enact-providers/src/azure.rs +96 -0
  213. package/crates/enact-providers/src/bridge.rs +221 -0
  214. package/crates/enact-providers/src/gemini.rs +227 -0
  215. package/crates/enact-providers/src/http.rs +78 -0
  216. package/crates/enact-providers/src/lib.rs +53 -0
  217. package/crates/enact-providers/src/openai_compatible.rs +167 -0
  218. package/crates/enact-providers/src/openrouter.rs +33 -0
  219. package/crates/enact-runner/Cargo.toml +24 -0
  220. package/crates/enact-runner/README.md +76 -0
  221. package/crates/enact-runner/src/compaction.rs +225 -0
  222. package/crates/enact-runner/src/config.rs +118 -0
  223. package/crates/enact-runner/src/lib.rs +63 -0
  224. package/crates/enact-runner/src/loop_driver.rs +414 -0
  225. package/crates/enact-runner/src/parser.rs +421 -0
  226. package/crates/enact-runner/src/retry.rs +262 -0
  227. package/crates/enact-runner/tests/integration.rs +278 -0
  228. package/crates/enact-security/Cargo.toml +22 -0
  229. package/crates/enact-security/src/audit.rs +375 -0
  230. package/crates/enact-security/src/lib.rs +37 -0
  231. package/crates/enact-security/src/policy.rs +406 -0
  232. package/crates/enact-skills/Cargo.toml +25 -0
  233. package/crates/enact-skills/src/lib.rs +506 -0
  234. package/crates/enact-tools/Cargo.toml +22 -0
  235. package/crates/enact-tools/src/file_read.rs +166 -0
  236. package/crates/enact-tools/src/file_write.rs +216 -0
  237. package/crates/enact-tools/src/git_operations.rs +513 -0
  238. package/crates/enact-tools/src/http_request.rs +417 -0
  239. package/crates/enact-tools/src/lib.rs +104 -0
  240. package/crates/enact-tools/src/security.rs +227 -0
  241. package/crates/enact-tools/src/shell.rs +191 -0
  242. package/crates/enact-tools/src/traits.rs +159 -0
  243. package/docs/Makefile +74 -0
  244. package/docs/config.toml +62 -0
  245. package/docs/content/_index.md +174 -0
  246. package/docs/content/a2a/_index.md +431 -0
  247. package/docs/content/api/_index.md +323 -0
  248. package/docs/content/channels/_index.md +160 -0
  249. package/docs/content/channels/teams.md +205 -0
  250. package/docs/content/channels/telegram.md +182 -0
  251. package/docs/content/channels/webhook.md +423 -0
  252. package/docs/content/channels/whatsapp.md +240 -0
  253. package/docs/content/cli/_index.md +261 -0
  254. package/docs/content/concepts/_index.md +273 -0
  255. package/docs/content/configuration/_index.md +241 -0
  256. package/docs/content/cron/_index.md +248 -0
  257. package/docs/content/developers/_index.md +278 -0
  258. package/docs/content/getting-started/_index.md +180 -0
  259. package/docs/content/installation/_index.md +186 -0
  260. package/docs/content/installation/uninstall.md +101 -0
  261. package/docs/content/installation/updating.md +120 -0
  262. package/docs/content/mcp/_index.md +215 -0
  263. package/docs/content/memory/_index.md +163 -0
  264. package/docs/content/oauth/_index.md +515 -0
  265. package/docs/content/providers/_index.md +206 -0
  266. package/docs/content/roadmap/_index.md +199 -0
  267. package/docs/content/security/_index.md +219 -0
  268. package/docs/content/skills/_index.md +228 -0
  269. package/docs/content/tools/_index.md +485 -0
  270. package/docs/content/troubleshooting/_index.md +259 -0
  271. package/docs/content/yaml-schema/_index.md +294 -0
  272. package/docs/static/giallo-dark.css +91 -0
  273. package/docs/static/giallo-light.css +91 -0
  274. package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
  275. package/docs/themes/tanuki/LICENSE +21 -0
  276. package/docs/themes/tanuki/README.md +166 -0
  277. package/docs/themes/tanuki/examples/blog/config.toml +58 -0
  278. package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
  279. package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
  280. package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
  281. package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
  282. package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
  283. package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
  284. package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
  285. package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
  286. package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
  287. package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
  288. package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
  289. package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
  290. package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
  291. package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
  292. package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
  293. package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
  294. package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
  295. package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
  296. package/docs/themes/tanuki/examples/book/config.toml +17 -0
  297. package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
  298. package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
  299. package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
  300. package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
  301. package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
  302. package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
  303. package/docs/themes/tanuki/examples/book/content/print.md +6 -0
  304. package/docs/themes/tanuki/examples/docs/config.toml +28 -0
  305. package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
  306. package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
  307. package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
  308. package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
  309. package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
  310. package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
  311. package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
  312. package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
  313. package/docs/themes/tanuki/examples/index.html +2104 -0
  314. package/docs/themes/tanuki/mise.toml +108 -0
  315. package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
  316. package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
  317. package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
  318. package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
  319. package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
  320. package/docs/themes/tanuki/sass/components/_code.scss +457 -0
  321. package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
  322. package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
  323. package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
  324. package/docs/themes/tanuki/sass/components/_print.scss +237 -0
  325. package/docs/themes/tanuki/sass/components/_search.scss +224 -0
  326. package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
  327. package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
  328. package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
  329. package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
  330. package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
  331. package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
  332. package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
  333. package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
  334. package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
  335. package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
  336. package/docs/themes/tanuki/sass/style.scss +47 -0
  337. package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
  338. package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
  339. package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
  340. package/docs/themes/tanuki/screenshot.png +0 -0
  341. package/docs/themes/tanuki/sentinel.kdl +59 -0
  342. package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
  343. package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
  344. package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
  345. package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
  346. package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
  347. package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
  348. package/docs/themes/tanuki/static/js/anchors.js +18 -0
  349. package/docs/themes/tanuki/static/js/app.js +274 -0
  350. package/docs/themes/tanuki/static/js/code.js +394 -0
  351. package/docs/themes/tanuki/static/js/navigation.js +778 -0
  352. package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
  353. package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
  354. package/docs/themes/tanuki/static/js/search.js +215 -0
  355. package/docs/themes/tanuki/static/js/theme.js +169 -0
  356. package/docs/themes/tanuki/static/syntax-dark.css +151 -0
  357. package/docs/themes/tanuki/static/syntax-light.css +151 -0
  358. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
  359. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
  360. package/docs/themes/tanuki/templates/404.html +52 -0
  361. package/docs/themes/tanuki/templates/base.html +428 -0
  362. package/docs/themes/tanuki/templates/blog.html +66 -0
  363. package/docs/themes/tanuki/templates/home.html +108 -0
  364. package/docs/themes/tanuki/templates/index.html +178 -0
  365. package/docs/themes/tanuki/templates/landing.html +168 -0
  366. package/docs/themes/tanuki/templates/macros/nav.html +128 -0
  367. package/docs/themes/tanuki/templates/macros/posts.html +101 -0
  368. package/docs/themes/tanuki/templates/macros/ui.html +159 -0
  369. package/docs/themes/tanuki/templates/page.html +135 -0
  370. package/docs/themes/tanuki/templates/partials/footer.html +38 -0
  371. package/docs/themes/tanuki/templates/partials/header.html +366 -0
  372. package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
  373. package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
  374. package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
  375. package/docs/themes/tanuki/templates/partials/search.html +52 -0
  376. package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
  377. package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
  378. package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
  379. package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
  380. package/docs/themes/tanuki/templates/print.html +244 -0
  381. package/docs/themes/tanuki/templates/section.html +186 -0
  382. package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
  383. package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
  384. package/docs/themes/tanuki/theme.toml +58 -0
  385. package/examples/hello-agent.rs +55 -0
  386. package/package.json +36 -0
  387. package/proto/config.proto +60 -0
  388. package/proto/events.proto +0 -0
  389. package/proto/runtime.proto +215 -0
@@ -0,0 +1,2104 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark" prefix="og: https://ogp.me/ns#">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="color-scheme" content="dark light">
7
+
8
+ <!-- Primary Meta Tags -->
9
+ <title>Tanuki - An Opinionated Zola Theme for Documentation, Books, and Blogs</title>
10
+ <meta name="title" content="Tanuki - An Opinionated Zola Theme for Documentation, Books, and Blogs">
11
+ <meta name="description" content="A beautiful, accessible Zola theme with four modes: documentation sites, distraction-free books, card-based blogs, and product landing pages. Features Catppuccin colors, Geist typography, dark/light themes, and built-in search.">
12
+ <meta name="keywords" content="Zola theme, static site generator, documentation theme, book theme, blog theme, Catppuccin, Geist font, dark mode, light mode, responsive design, accessible, open source">
13
+ <meta name="author" content="raskell.io">
14
+ <meta name="robots" content="index, follow">
15
+ <meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
16
+ <meta name="bingbot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
17
+
18
+ <!-- Canonical URL -->
19
+ <link rel="canonical" href="https://tanuki.raskell.io/">
20
+
21
+ <!-- Open Graph / Facebook -->
22
+ <meta property="og:type" content="website">
23
+ <meta property="og:url" content="https://tanuki.raskell.io/">
24
+ <meta property="og:title" content="Tanuki - An Opinionated Zola Theme">
25
+ <meta property="og:description" content="Beautiful, accessible Zola theme with four modes: documentation, books, blogs, and product landing pages. Features Catppuccin colors, Geist typography, and built-in search.">
26
+ <meta property="og:image" content="https://tanuki.raskell.io/og-image.png">
27
+ <meta property="og:image:width" content="1200">
28
+ <meta property="og:image:height" content="630">
29
+ <meta property="og:image:alt" content="Tanuki theme preview showing documentation, book, and blog modes">
30
+ <meta property="og:site_name" content="Tanuki">
31
+ <meta property="og:locale" content="en_US">
32
+
33
+ <!-- Twitter Card -->
34
+ <meta name="twitter:card" content="summary_large_image">
35
+ <meta name="twitter:url" content="https://tanuki.raskell.io/">
36
+ <meta name="twitter:title" content="Tanuki - An Opinionated Zola Theme">
37
+ <meta name="twitter:description" content="Beautiful, accessible Zola theme with four modes: documentation, books, blogs, and product landing pages. Features Catppuccin colors, Geist typography, and built-in search.">
38
+ <meta name="twitter:image" content="https://tanuki.raskell.io/og-image.png">
39
+ <meta name="twitter:image:alt" content="Tanuki theme preview showing documentation, book, and blog modes">
40
+
41
+ <!-- Favicon -->
42
+ <link rel="icon" type="image/avif" href="./docs/img/tanuki-icon.avif">
43
+ <link rel="apple-touch-icon" href="./docs/img/tanuki-icon.avif">
44
+
45
+ <!-- JSON-LD Structured Data for SEO and AEO -->
46
+ <script type="application/ld+json">
47
+ {
48
+ "@context": "https://schema.org",
49
+ "@graph": [
50
+ {
51
+ "@type": "WebSite",
52
+ "@id": "https://tanuki.raskell.io/#website",
53
+ "url": "https://tanuki.raskell.io/",
54
+ "name": "Tanuki",
55
+ "description": "An opinionated Zola theme for documentation, books, and blogs",
56
+ "publisher": {
57
+ "@id": "https://raskell.io/#organization"
58
+ },
59
+ "inLanguage": "en-US"
60
+ },
61
+ {
62
+ "@type": "WebPage",
63
+ "@id": "https://tanuki.raskell.io/#webpage",
64
+ "url": "https://tanuki.raskell.io/",
65
+ "name": "Tanuki - An Opinionated Zola Theme for Documentation, Books, and Blogs",
66
+ "isPartOf": {
67
+ "@id": "https://tanuki.raskell.io/#website"
68
+ },
69
+ "about": {
70
+ "@id": "https://tanuki.raskell.io/#software"
71
+ },
72
+ "description": "A beautiful, accessible Zola theme with four modes: documentation sites, distraction-free books, card-based blogs, and product landing pages.",
73
+ "inLanguage": "en-US",
74
+ "potentialAction": [
75
+ {
76
+ "@type": "ReadAction",
77
+ "target": ["https://tanuki.raskell.io/docs/"]
78
+ }
79
+ ]
80
+ },
81
+ {
82
+ "@type": "SoftwareApplication",
83
+ "@id": "https://tanuki.raskell.io/#software",
84
+ "name": "Tanuki",
85
+ "description": "An opinionated Zola theme designed for documentation sites, technical books, developer blogs, and product landing pages. Features four distinct modes, Catppuccin color palette, Geist typography, resizable sidebar, dark/light themes, and built-in search.",
86
+ "applicationCategory": "DeveloperApplication",
87
+ "applicationSubCategory": "Static Site Theme",
88
+ "operatingSystem": "Any",
89
+ "offers": {
90
+ "@type": "Offer",
91
+ "price": "0",
92
+ "priceCurrency": "USD"
93
+ },
94
+ "license": "https://opensource.org/licenses/MIT",
95
+ "isAccessibleForFree": true,
96
+ "creator": {
97
+ "@type": "Organization",
98
+ "name": "raskell.io",
99
+ "url": "https://raskell.io"
100
+ },
101
+ "codeRepository": "https://github.com/raskell-io/tanuki",
102
+ "programmingLanguage": ["HTML", "CSS", "JavaScript", "Tera"],
103
+ "runtimePlatform": "Zola Static Site Generator",
104
+ "featureList": [
105
+ "Four layout modes: Documentation, Book, Blog, and Product",
106
+ "Catppuccin color palette with soothing pastel colors",
107
+ "Geist variable font for clean typography",
108
+ "Dark and light theme with system preference detection",
109
+ "Resizable sidebar with localStorage persistence",
110
+ "Built-in full-text search",
111
+ "Fully responsive and mobile-first design",
112
+ "Keyboard navigation support",
113
+ "Copy-to-clipboard anchor links",
114
+ "Version picker for documentation",
115
+ "RSS feed generation",
116
+ "Syntax highlighting for code blocks"
117
+ ],
118
+ "screenshot": "https://tanuki.raskell.io/og-image.png",
119
+ "softwareVersion": "1.0.0",
120
+ "datePublished": "2024-01-01",
121
+ "keywords": ["Zola", "theme", "documentation", "book", "blog", "static site", "Catppuccin", "Geist", "dark mode"]
122
+ },
123
+ {
124
+ "@type": "ItemList",
125
+ "@id": "https://tanuki.raskell.io/#modes",
126
+ "name": "Tanuki Theme Modes",
127
+ "description": "Four distinct layout modes available in the Tanuki theme",
128
+ "numberOfItems": 4,
129
+ "itemListElement": [
130
+ {
131
+ "@type": "ListItem",
132
+ "position": 1,
133
+ "name": "Documentation Mode",
134
+ "description": "A resizable sidebar with hierarchical navigation, integrated search, and version picker. Designed for reference material that users need to navigate quickly. Best for API docs, guides, references, wikis, and knowledge bases.",
135
+ "url": "https://tanuki.raskell.io/docs/"
136
+ },
137
+ {
138
+ "@type": "ListItem",
139
+ "position": 2,
140
+ "name": "Book Mode",
141
+ "description": "A distraction-free reading experience with full-screen chapter overlay and keyboard navigation. Maximizes content width for comfortable long-form reading. Best for tutorials, e-books, courses, technical books, and manuals.",
142
+ "url": "https://tanuki.raskell.io/book/"
143
+ },
144
+ {
145
+ "@type": "ListItem",
146
+ "position": 3,
147
+ "name": "Blog Mode",
148
+ "description": "Card-based listing with tags, dates, and reading time estimates. Individual posts with clean typography optimized for articles. Best for personal blogs, dev logs, newsletters, and announcements.",
149
+ "url": "https://tanuki.raskell.io/blog/"
150
+ },
151
+ {
152
+ "@type": "ListItem",
153
+ "position": 4,
154
+ "name": "Product Mode",
155
+ "description": "Full-width landing pages with hero sections, feature grids, install commands, and custom footers. Designed to showcase CLI tools, libraries, SaaS products, and open source projects.",
156
+ "url": "https://sango.raskell.io/"
157
+ }
158
+ ]
159
+ },
160
+ {
161
+ "@type": "FAQPage",
162
+ "@id": "https://tanuki.raskell.io/#faq",
163
+ "mainEntity": [
164
+ {
165
+ "@type": "Question",
166
+ "name": "What is Tanuki?",
167
+ "acceptedAnswer": {
168
+ "@type": "Answer",
169
+ "text": "Tanuki is an opinionated Zola theme designed for documentation sites, technical books, developer blogs, and product landing pages. It features four distinct layout modes, Catppuccin colors, Geist typography, and is fully accessible and responsive."
170
+ }
171
+ },
172
+ {
173
+ "@type": "Question",
174
+ "name": "What are the four modes in Tanuki?",
175
+ "acceptedAnswer": {
176
+ "@type": "Answer",
177
+ "text": "Tanuki offers four modes: (1) Documentation mode with a resizable sidebar and hierarchical navigation for API docs and guides, (2) Book mode with distraction-free reading and keyboard navigation for tutorials and e-books, (3) Blog mode with card-based listings for personal blogs and newsletters, and (4) Product mode with full-width landing pages for CLI tools, libraries, and open source projects."
178
+ }
179
+ },
180
+ {
181
+ "@type": "Question",
182
+ "name": "What color scheme does Tanuki use?",
183
+ "acceptedAnswer": {
184
+ "@type": "Answer",
185
+ "text": "Tanuki uses the Catppuccin color palette, featuring soothing pastel colors with perfect contrast ratios for accessibility. It supports both dark (Mocha) and light (Latte) themes with automatic system preference detection."
186
+ }
187
+ },
188
+ {
189
+ "@type": "Question",
190
+ "name": "Is Tanuki free to use?",
191
+ "acceptedAnswer": {
192
+ "@type": "Answer",
193
+ "text": "Yes, Tanuki is completely free and open source. It is licensed under the MIT license and available on GitHub at github.com/raskell-io/tanuki."
194
+ }
195
+ },
196
+ {
197
+ "@type": "Question",
198
+ "name": "What static site generator does Tanuki work with?",
199
+ "acceptedAnswer": {
200
+ "@type": "Answer",
201
+ "text": "Tanuki is built specifically for Zola, a fast static site generator written in Rust. Zola compiles your content into static HTML files for fast, secure hosting anywhere."
202
+ }
203
+ }
204
+ ]
205
+ },
206
+ {
207
+ "@type": "Organization",
208
+ "@id": "https://raskell.io/#organization",
209
+ "name": "raskell.io",
210
+ "url": "https://raskell.io",
211
+ "logo": "https://raskell.io/logo.png"
212
+ }
213
+ ]
214
+ }
215
+ </script>
216
+
217
+ <!-- Additional SEO: Preconnect and DNS Prefetch -->
218
+ <link rel="preconnect" href="https://fonts.googleapis.com">
219
+ <link rel="dns-prefetch" href="https://fonts.googleapis.com">
220
+ <style>
221
+ :root {
222
+ --ctp-mocha-base: #1e1e2e;
223
+ --ctp-mocha-mantle: #181825;
224
+ --ctp-mocha-crust: #11111b;
225
+ --ctp-mocha-text: #cdd6f4;
226
+ --ctp-mocha-subtext1: #bac2de;
227
+ --ctp-mocha-subtext0: #a6adc8;
228
+ --ctp-mocha-overlay2: #9399b2;
229
+ --ctp-mocha-overlay1: #7f849c;
230
+ --ctp-mocha-overlay0: #6c7086;
231
+ --ctp-mocha-surface2: #585b70;
232
+ --ctp-mocha-surface1: #45475a;
233
+ --ctp-mocha-surface0: #313244;
234
+ --ctp-mocha-mauve: #cba6f7;
235
+ --ctp-mocha-pink: #f5c2e7;
236
+ --ctp-mocha-teal: #94e2d5;
237
+ --ctp-mocha-green: #a6e3a1;
238
+ --ctp-mocha-peach: #fab387;
239
+ --ctp-mocha-blue: #89b4fa;
240
+ --ctp-mocha-lavender: #b4befe;
241
+ --ctp-mocha-yellow: #f9e2af;
242
+
243
+ --ctp-latte-base: #eff1f5;
244
+ --ctp-latte-mantle: #e6e9ef;
245
+ --ctp-latte-crust: #dce0e8;
246
+ --ctp-latte-text: #4c4f69;
247
+ --ctp-latte-subtext1: #5c5f77;
248
+ --ctp-latte-subtext0: #6c6f85;
249
+ --ctp-latte-overlay2: #7c7f93;
250
+ --ctp-latte-surface0: #ccd0da;
251
+ --ctp-latte-mauve: #8839ef;
252
+ --ctp-latte-pink: #ea76cb;
253
+ --ctp-latte-teal: #179299;
254
+ --ctp-latte-green: #40a02b;
255
+ --ctp-latte-peach: #fe640b;
256
+ --ctp-latte-blue: #1e66f5;
257
+ --ctp-latte-lavender: #7287fd;
258
+ --ctp-latte-yellow: #df8e1d;
259
+ }
260
+
261
+ [data-theme="dark"] {
262
+ --bg: var(--ctp-mocha-base);
263
+ --bg-alt: var(--ctp-mocha-mantle);
264
+ --bg-surface: var(--ctp-mocha-surface0);
265
+ --bg-subtle: var(--ctp-mocha-mantle);
266
+ --text: var(--ctp-mocha-text);
267
+ --text-muted: var(--ctp-mocha-subtext0);
268
+ --text-subtle: var(--ctp-mocha-overlay1);
269
+ --border: var(--ctp-mocha-surface0);
270
+ --border-strong: var(--ctp-mocha-surface1);
271
+ --accent: var(--ctp-mocha-mauve);
272
+ --accent-hover: var(--ctp-mocha-pink);
273
+ --card-bg: var(--ctp-mocha-surface0);
274
+ --card-hover: var(--ctp-mocha-surface1);
275
+ --docs-color: var(--ctp-mocha-blue);
276
+ --book-color: var(--ctp-mocha-green);
277
+ --blog-color: var(--ctp-mocha-peach);
278
+ --product-color: var(--ctp-mocha-mauve);
279
+ }
280
+
281
+ [data-theme="light"] {
282
+ --bg: var(--ctp-latte-base);
283
+ --bg-alt: var(--ctp-latte-mantle);
284
+ --bg-surface: var(--ctp-latte-surface0);
285
+ --bg-subtle: var(--ctp-latte-crust);
286
+ --text: var(--ctp-latte-text);
287
+ --text-muted: var(--ctp-latte-subtext0);
288
+ --text-subtle: var(--ctp-latte-overlay2);
289
+ --border: var(--ctp-latte-crust);
290
+ --border-strong: var(--ctp-latte-surface1);
291
+ --accent: var(--ctp-latte-mauve);
292
+ --accent-hover: var(--ctp-latte-pink);
293
+ --card-bg: var(--ctp-latte-mantle);
294
+ --card-hover: var(--ctp-latte-crust);
295
+ --docs-color: var(--ctp-latte-blue);
296
+ --book-color: var(--ctp-latte-green);
297
+ --blog-color: var(--ctp-latte-peach);
298
+ --product-color: var(--ctp-latte-mauve);
299
+ }
300
+
301
+ * {
302
+ margin: 0;
303
+ padding: 0;
304
+ box-sizing: border-box;
305
+ }
306
+
307
+ @font-face {
308
+ font-family: 'Geist';
309
+ src: url('./docs/fonts/Geist-Variable.woff2') format('woff2');
310
+ font-weight: 100 900;
311
+ font-style: normal;
312
+ font-display: swap;
313
+ }
314
+
315
+ @font-face {
316
+ font-family: 'Geist Mono';
317
+ src: url('./docs/fonts/GeistMono-Variable.woff2') format('woff2');
318
+ font-weight: 100 900;
319
+ font-style: normal;
320
+ font-display: swap;
321
+ }
322
+
323
+ body {
324
+ font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
325
+ background: var(--bg);
326
+ color: var(--text);
327
+ min-height: 100vh;
328
+ line-height: 1.6;
329
+ }
330
+
331
+ /* Skip Link for Accessibility */
332
+ .skip-link {
333
+ position: absolute;
334
+ top: -100%;
335
+ left: 50%;
336
+ transform: translateX(-50%);
337
+ z-index: 9999;
338
+ padding: 0.75rem 1.5rem;
339
+ background: var(--accent);
340
+ color: var(--bg);
341
+ font-weight: 600;
342
+ text-decoration: none;
343
+ border-radius: 0 0 8px 8px;
344
+ transition: top 0.2s ease;
345
+ }
346
+
347
+ .skip-link:focus {
348
+ top: 0;
349
+ outline: 2px solid var(--text);
350
+ outline-offset: 2px;
351
+ }
352
+
353
+ .container {
354
+ max-width: 1000px;
355
+ width: 100%;
356
+ margin: 0 auto;
357
+ padding: 0 1.5rem;
358
+ }
359
+
360
+ /* Hero Section */
361
+ .hero {
362
+ padding: 4rem 0 3rem;
363
+ text-align: center;
364
+ }
365
+
366
+ .hero__logo {
367
+ font-size: 3rem;
368
+ font-weight: 700;
369
+ margin-bottom: 1rem;
370
+ display: flex;
371
+ align-items: center;
372
+ justify-content: center;
373
+ gap: 0.75rem;
374
+ }
375
+
376
+ .hero__logo img {
377
+ width: 48px;
378
+ height: 48px;
379
+ object-fit: contain;
380
+ }
381
+
382
+ .hero__logo rt {
383
+ font-size: 0.4em;
384
+ color: var(--text-muted);
385
+ font-weight: 400;
386
+ }
387
+
388
+ .hero__title {
389
+ font-size: 2.5rem;
390
+ font-weight: 700;
391
+ margin-bottom: 1rem;
392
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
393
+ -webkit-background-clip: text;
394
+ -webkit-text-fill-color: transparent;
395
+ background-clip: text;
396
+ }
397
+
398
+ .hero__subtitle {
399
+ color: var(--text-muted);
400
+ font-size: 1.25rem;
401
+ max-width: 600px;
402
+ margin: 0 auto 1.5rem;
403
+ }
404
+
405
+ .hero__tagline {
406
+ display: inline-flex;
407
+ align-items: center;
408
+ gap: 0.5rem;
409
+ padding: 0.5rem 1rem;
410
+ background: var(--bg-surface);
411
+ border: 1px solid var(--border);
412
+ border-radius: 9999px;
413
+ font-size: 0.875rem;
414
+ color: var(--text-muted);
415
+ }
416
+
417
+ .hero__tagline strong {
418
+ color: var(--accent);
419
+ font-weight: 600;
420
+ }
421
+
422
+ /* Install Widget - Sentinel-inspired */
423
+ .install-widget {
424
+ display: flex;
425
+ align-items: center;
426
+ justify-content: center;
427
+ gap: 0.5rem;
428
+ margin-top: 2rem;
429
+ }
430
+
431
+ .install-selector {
432
+ position: relative;
433
+ z-index: 100;
434
+ }
435
+
436
+ .install-toggle {
437
+ display: flex;
438
+ align-items: center;
439
+ gap: 0.375rem;
440
+ padding: 0.75rem 1rem;
441
+ background: var(--bg-surface);
442
+ border: 1px solid var(--border-strong);
443
+ border-radius: 0.5rem;
444
+ color: var(--text);
445
+ cursor: pointer;
446
+ transition: all 0.15s ease;
447
+ }
448
+
449
+ .install-toggle:hover {
450
+ border-color: var(--accent);
451
+ }
452
+
453
+ .install-toggle:focus-visible {
454
+ outline: 2px solid var(--accent);
455
+ outline-offset: 2px;
456
+ }
457
+
458
+ .install-toggle-icon {
459
+ display: flex;
460
+ align-items: center;
461
+ justify-content: center;
462
+ }
463
+
464
+ .install-toggle-chevron {
465
+ color: var(--text-muted);
466
+ transition: transform 0.15s ease;
467
+ }
468
+
469
+ .install-dropdown.open + .install-toggle .install-toggle-chevron,
470
+ .install-selector:has(.install-dropdown.open) .install-toggle-chevron {
471
+ transform: rotate(180deg);
472
+ }
473
+
474
+ .install-dropdown {
475
+ position: absolute;
476
+ top: calc(100% + 0.375rem);
477
+ left: 0;
478
+ min-width: 140px;
479
+ background: var(--bg-surface);
480
+ border: 1px solid var(--border-strong);
481
+ border-radius: 0.5rem;
482
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
483
+ opacity: 0;
484
+ visibility: hidden;
485
+ transform: translateY(-8px);
486
+ transition: all 0.15s ease;
487
+ z-index: 9999;
488
+ }
489
+
490
+ .install-dropdown.open {
491
+ opacity: 1;
492
+ visibility: visible;
493
+ transform: translateY(0);
494
+ }
495
+
496
+ .install-option {
497
+ display: flex;
498
+ align-items: center;
499
+ gap: 0.5rem;
500
+ width: 100%;
501
+ padding: 0.625rem 1rem;
502
+ background: transparent;
503
+ border: none;
504
+ color: var(--text-muted);
505
+ font-family: var(--font-sans);
506
+ font-size: 0.875rem;
507
+ cursor: pointer;
508
+ transition: all 0.15s ease;
509
+ white-space: nowrap;
510
+ border-radius: 0.5rem;
511
+ }
512
+
513
+ .install-option:hover {
514
+ background: var(--bg-alt);
515
+ color: var(--text);
516
+ }
517
+
518
+ .install-option.active {
519
+ color: var(--accent);
520
+ background: color-mix(in srgb, var(--accent) 10%, transparent);
521
+ }
522
+
523
+ .install-option svg {
524
+ flex-shrink: 0;
525
+ }
526
+
527
+ .install-cmd-wrapper {
528
+ display: flex;
529
+ align-items: center;
530
+ gap: 0.75rem;
531
+ padding: 0.75rem 1rem;
532
+ background: var(--bg-subtle);
533
+ border: 1px solid var(--border-strong);
534
+ border-radius: 0.5rem;
535
+ transition: border-color 0.15s ease;
536
+ }
537
+
538
+ .install-cmd-wrapper:hover {
539
+ border-color: var(--accent);
540
+ }
541
+
542
+ .install-cmd {
543
+ font-family: 'Geist Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
544
+ font-size: 0.875rem;
545
+ color: var(--text);
546
+ white-space: nowrap;
547
+ user-select: all;
548
+ }
549
+
550
+ .copy-btn {
551
+ display: flex;
552
+ align-items: center;
553
+ justify-content: center;
554
+ padding: 0.375rem;
555
+ background: transparent;
556
+ border: none;
557
+ color: var(--text-muted);
558
+ cursor: pointer;
559
+ border-radius: 0.25rem;
560
+ transition: all 0.15s ease;
561
+ flex-shrink: 0;
562
+ }
563
+
564
+ .copy-btn:hover {
565
+ color: var(--text);
566
+ background: var(--bg-surface);
567
+ }
568
+
569
+ .copy-btn:focus-visible {
570
+ outline: 2px solid var(--accent);
571
+ outline-offset: 2px;
572
+ }
573
+
574
+ .copy-btn.copied {
575
+ color: var(--accent);
576
+ }
577
+
578
+ @keyframes confetti-burst {
579
+ 0% { transform: translate(-50%, -50%) translate(0, 0) scale(1); opacity: 1; }
580
+ 100% { transform: translate(-50%, -50%) translate(var(--tx), var(--ty)) scale(0); opacity: 0; }
581
+ }
582
+
583
+ /* Section */
584
+ .section {
585
+ padding: 4rem 0;
586
+ }
587
+
588
+ .section--alt {
589
+ background: var(--bg-alt);
590
+ }
591
+
592
+ .section__header {
593
+ text-align: center;
594
+ margin-bottom: 3rem;
595
+ }
596
+
597
+ .section__title {
598
+ font-size: clamp(1.5rem, 3vw, 2rem);
599
+ font-weight: 700;
600
+ margin-bottom: 0.5rem;
601
+ }
602
+
603
+ .section__description {
604
+ color: var(--text-muted);
605
+ font-size: 1.125rem;
606
+ }
607
+
608
+ /* Mode Cards - Sentinel-inspired */
609
+ .modes {
610
+ display: grid;
611
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
612
+ gap: 1.5rem;
613
+ margin-bottom: 2rem;
614
+ list-style: none;
615
+ }
616
+
617
+ .mode-card {
618
+ height: 100%;
619
+ }
620
+
621
+ .mode-card a {
622
+ display: flex;
623
+ flex-direction: column;
624
+ height: 100%;
625
+ padding: 2rem;
626
+ background: var(--bg-surface);
627
+ border: 1px solid var(--border);
628
+ border-radius: 12px;
629
+ text-decoration: none;
630
+ color: var(--text);
631
+ transition: all 0.25s ease;
632
+ }
633
+
634
+ .mode-card a:hover,
635
+ .mode-card a:focus {
636
+ border-color: var(--accent);
637
+ transform: translateY(-2px);
638
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
639
+ outline: none;
640
+ }
641
+
642
+ .mode-card a:focus-visible {
643
+ outline: 2px solid var(--accent);
644
+ outline-offset: 2px;
645
+ }
646
+
647
+ .mode-card--docs a:hover,
648
+ .mode-card--docs a:focus { border-color: var(--docs-color); }
649
+ .mode-card--book a:hover,
650
+ .mode-card--book a:focus { border-color: var(--book-color); }
651
+ .mode-card--blog a:hover,
652
+ .mode-card--blog a:focus { border-color: var(--blog-color); }
653
+ .mode-card--product a:hover,
654
+ .mode-card--product a:focus { border-color: var(--product-color); }
655
+
656
+ .mode-card__header {
657
+ display: flex;
658
+ align-items: center;
659
+ gap: 1rem;
660
+ margin-bottom: 1rem;
661
+ }
662
+
663
+ .mode-card__icon {
664
+ display: flex;
665
+ align-items: center;
666
+ justify-content: center;
667
+ width: 48px;
668
+ height: 48px;
669
+ background: var(--bg-alt);
670
+ border-radius: 8px;
671
+ transition: all 0.25s ease;
672
+ }
673
+
674
+ .mode-card--docs .mode-card__icon {
675
+ color: var(--docs-color);
676
+ }
677
+
678
+ .mode-card--book .mode-card__icon {
679
+ color: var(--book-color);
680
+ }
681
+
682
+ .mode-card--blog .mode-card__icon {
683
+ color: var(--blog-color);
684
+ }
685
+
686
+ .mode-card--product .mode-card__icon {
687
+ color: var(--product-color);
688
+ }
689
+
690
+ .mode-card--docs a:hover .mode-card__icon,
691
+ .mode-card--docs a:focus .mode-card__icon {
692
+ background: var(--docs-color);
693
+ color: var(--bg);
694
+ }
695
+
696
+ .mode-card--book a:hover .mode-card__icon,
697
+ .mode-card--book a:focus .mode-card__icon {
698
+ background: var(--book-color);
699
+ color: var(--bg);
700
+ }
701
+
702
+ .mode-card--blog a:hover .mode-card__icon,
703
+ .mode-card--blog a:focus .mode-card__icon {
704
+ background: var(--blog-color);
705
+ color: var(--bg);
706
+ }
707
+
708
+ .mode-card--product a:hover .mode-card__icon,
709
+ .mode-card--product a:focus .mode-card__icon {
710
+ background: var(--product-color);
711
+ color: var(--bg);
712
+ }
713
+
714
+ .mode-card__icon svg {
715
+ width: 24px;
716
+ height: 24px;
717
+ }
718
+
719
+ .mode-card__title {
720
+ font-size: 1.125rem;
721
+ font-weight: 600;
722
+ }
723
+
724
+ .mode-card__description {
725
+ color: var(--text-muted);
726
+ font-size: 0.9375rem;
727
+ line-height: 1.6;
728
+ margin-bottom: 1.5rem;
729
+ flex: 1;
730
+ }
731
+
732
+ .mode-card__use-cases {
733
+ font-size: 0.8125rem;
734
+ color: var(--text-subtle);
735
+ padding-top: 1rem;
736
+ border-top: 1px solid var(--border);
737
+ }
738
+
739
+ .mode-card__use-cases strong {
740
+ color: var(--text-muted);
741
+ font-weight: 500;
742
+ }
743
+
744
+ /* Configuration Section */
745
+ .config-tabs {
746
+ display: flex;
747
+ gap: 0.5rem;
748
+ margin-bottom: 1.5rem;
749
+ justify-content: center;
750
+ flex-wrap: wrap;
751
+ }
752
+
753
+ .config-tab {
754
+ display: flex;
755
+ align-items: center;
756
+ gap: 0.5rem;
757
+ padding: 0.75rem 1.25rem;
758
+ background: var(--bg-surface);
759
+ border: 2px solid var(--border);
760
+ border-radius: 8px;
761
+ color: var(--text-muted);
762
+ font-family: inherit;
763
+ font-size: 0.9375rem;
764
+ font-weight: 500;
765
+ cursor: pointer;
766
+ transition: all 0.2s ease;
767
+ }
768
+
769
+ .config-tab:hover {
770
+ border-color: var(--text-muted);
771
+ color: var(--text);
772
+ }
773
+
774
+ .config-tab.active {
775
+ border-color: var(--accent);
776
+ color: var(--text);
777
+ background: color-mix(in srgb, var(--accent) 10%, var(--bg-surface));
778
+ }
779
+
780
+ .config-tab--docs.active { border-color: var(--docs-color); }
781
+ .config-tab--book.active { border-color: var(--book-color); }
782
+ .config-tab--blog.active { border-color: var(--blog-color); }
783
+ .config-tab--product.active { border-color: var(--product-color); }
784
+
785
+ .config-tab svg {
786
+ width: 18px;
787
+ height: 18px;
788
+ }
789
+
790
+ .config-tab--docs svg { color: var(--docs-color); }
791
+ .config-tab--book svg { color: var(--book-color); }
792
+ .config-tab--blog svg { color: var(--blog-color); }
793
+ .config-tab--product svg { color: var(--product-color); }
794
+
795
+ .config-panel {
796
+ display: none;
797
+ position: relative;
798
+ }
799
+
800
+ .config-panel.active {
801
+ display: block;
802
+ }
803
+
804
+ .config-code {
805
+ position: relative;
806
+ background: var(--bg-surface);
807
+ border: 1px solid var(--border);
808
+ border-radius: 12px;
809
+ overflow: hidden;
810
+ }
811
+
812
+ .config-code__header {
813
+ display: flex;
814
+ align-items: center;
815
+ justify-content: space-between;
816
+ padding: 0.75rem 1rem;
817
+ background: var(--bg-alt);
818
+ border-bottom: 1px solid var(--border);
819
+ }
820
+
821
+ .config-code__filename {
822
+ font-family: 'Geist Mono', ui-monospace, monospace;
823
+ font-size: 0.8125rem;
824
+ color: var(--text-muted);
825
+ }
826
+
827
+ .config-code__copy {
828
+ display: flex;
829
+ align-items: center;
830
+ gap: 0.375rem;
831
+ padding: 0.375rem 0.75rem;
832
+ background: transparent;
833
+ border: 1px solid var(--border);
834
+ border-radius: 6px;
835
+ color: var(--text-muted);
836
+ font-family: inherit;
837
+ font-size: 0.75rem;
838
+ cursor: pointer;
839
+ transition: all 0.15s ease;
840
+ }
841
+
842
+ .config-code__copy:hover {
843
+ border-color: var(--accent);
844
+ color: var(--text);
845
+ }
846
+
847
+ .config-code__copy.copied {
848
+ border-color: var(--ctp-mocha-green);
849
+ color: var(--ctp-mocha-green);
850
+ }
851
+
852
+ [data-theme="light"] .config-code__copy.copied {
853
+ border-color: var(--ctp-latte-green);
854
+ color: var(--ctp-latte-green);
855
+ }
856
+
857
+ .config-code__copy svg {
858
+ width: 14px;
859
+ height: 14px;
860
+ }
861
+
862
+ .config-code pre {
863
+ margin: 0;
864
+ padding: 1.25rem;
865
+ overflow-x: auto;
866
+ font-family: 'Geist Mono', ui-monospace, monospace;
867
+ font-size: 0.8125rem;
868
+ line-height: 1.6;
869
+ }
870
+
871
+ .config-code code {
872
+ font-family: inherit;
873
+ color: var(--text);
874
+ }
875
+
876
+ /* TOML syntax highlighting */
877
+ .toml-comment { color: var(--text-subtle); font-style: italic; }
878
+ .toml-key { color: var(--ctp-mocha-blue); }
879
+ .toml-string { color: var(--ctp-mocha-green); }
880
+ .toml-bool, .toml-number { color: var(--ctp-mocha-peach); }
881
+ .toml-table { color: var(--ctp-mocha-mauve); font-weight: 600; }
882
+ .toml-array-table { color: var(--ctp-mocha-pink); font-weight: 600; }
883
+
884
+ [data-theme="light"] .toml-key { color: var(--ctp-latte-blue); }
885
+ [data-theme="light"] .toml-string { color: var(--ctp-latte-green); }
886
+ [data-theme="light"] .toml-bool, [data-theme="light"] .toml-number { color: var(--ctp-latte-peach); }
887
+ [data-theme="light"] .toml-table { color: var(--ctp-latte-mauve); }
888
+ [data-theme="light"] .toml-array-table { color: var(--ctp-latte-pink); }
889
+
890
+ /* Features - Sentinel feature-card style */
891
+ .features {
892
+ display: grid;
893
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
894
+ gap: 1.5rem;
895
+ list-style: none;
896
+ padding: 0;
897
+ margin: 0;
898
+ }
899
+
900
+ .feature {
901
+ padding: 2rem;
902
+ background: var(--bg);
903
+ border: 1px solid var(--border);
904
+ border-radius: 12px;
905
+ transition: all 0.25s ease;
906
+ }
907
+
908
+ .feature:hover {
909
+ border-color: var(--accent);
910
+ transform: translateY(-2px);
911
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
912
+ }
913
+
914
+ .feature__icon {
915
+ display: flex;
916
+ align-items: center;
917
+ justify-content: center;
918
+ width: 48px;
919
+ height: 48px;
920
+ margin-bottom: 1rem;
921
+ background: var(--bg-surface);
922
+ border-radius: 8px;
923
+ color: var(--accent);
924
+ }
925
+
926
+ .feature__icon svg {
927
+ width: 24px;
928
+ height: 24px;
929
+ }
930
+
931
+ .feature__content {
932
+ flex: 1;
933
+ }
934
+
935
+ .feature__title {
936
+ font-size: 1.125rem;
937
+ font-weight: 600;
938
+ margin-bottom: 0.5rem;
939
+ }
940
+
941
+ .feature__description {
942
+ font-size: 0.9375rem;
943
+ color: var(--text-muted);
944
+ line-height: 1.6;
945
+ }
946
+
947
+ /* Footer - Sentinel style */
948
+ .footer {
949
+ position: relative;
950
+ padding: 4rem 0 2rem;
951
+ background: var(--bg-alt);
952
+ border-top: 1px solid var(--border);
953
+ }
954
+
955
+ .footer__content {
956
+ display: grid;
957
+ grid-template-columns: 1fr auto;
958
+ gap: 4rem;
959
+ margin-bottom: 2rem;
960
+ }
961
+
962
+ @media (max-width: 768px) {
963
+ .footer__content {
964
+ grid-template-columns: 1fr;
965
+ gap: 2rem;
966
+ }
967
+ }
968
+
969
+ .footer__brand {
970
+ display: flex;
971
+ align-items: flex-start;
972
+ gap: 1.5rem;
973
+ }
974
+
975
+ @media (max-width: 768px) {
976
+ .footer__brand {
977
+ flex-direction: column;
978
+ align-items: center;
979
+ text-align: center;
980
+ }
981
+ }
982
+
983
+ .footer__logo-wrapper {
984
+ position: relative;
985
+ width: 64px;
986
+ height: 64px;
987
+ flex-shrink: 0;
988
+ }
989
+
990
+ .footer__logo-glow {
991
+ position: absolute;
992
+ top: 50%;
993
+ left: 50%;
994
+ transform: translate(-50%, -50%);
995
+ width: 48px;
996
+ height: 48px;
997
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
998
+ filter: blur(12px);
999
+ opacity: 0.4;
1000
+ border-radius: 50%;
1001
+ animation: footer-pulse 4s ease-in-out infinite;
1002
+ }
1003
+
1004
+ @keyframes footer-pulse {
1005
+ 0%, 100% {
1006
+ transform: translate(-50%, -50%) scale(1);
1007
+ opacity: 0.4;
1008
+ }
1009
+ 50% {
1010
+ transform: translate(-50%, -50%) scale(1.1);
1011
+ opacity: 0.55;
1012
+ }
1013
+ }
1014
+
1015
+ .footer__logo {
1016
+ position: relative;
1017
+ width: 64px;
1018
+ height: 64px;
1019
+ object-fit: contain;
1020
+ z-index: 1;
1021
+ animation: footer-float 5s ease-in-out infinite;
1022
+ }
1023
+
1024
+ @keyframes footer-float {
1025
+ 0%, 100% { transform: translateY(0); }
1026
+ 50% { transform: translateY(-6px); }
1027
+ }
1028
+
1029
+ .footer__tagline {
1030
+ display: flex;
1031
+ flex-direction: column;
1032
+ gap: 0.5rem;
1033
+ max-width: 400px;
1034
+ }
1035
+
1036
+ .footer__title {
1037
+ font-size: 1.125rem;
1038
+ font-weight: 600;
1039
+ color: var(--text);
1040
+ line-height: 1.4;
1041
+ }
1042
+
1043
+ .footer__description {
1044
+ font-size: 0.875rem;
1045
+ color: var(--text-muted);
1046
+ line-height: 1.6;
1047
+ }
1048
+
1049
+ .footer__links {
1050
+ display: flex;
1051
+ gap: 4rem;
1052
+ }
1053
+
1054
+ @media (max-width: 768px) {
1055
+ .footer__links {
1056
+ gap: 2rem;
1057
+ justify-content: center;
1058
+ }
1059
+ }
1060
+
1061
+ .footer__col {
1062
+ display: flex;
1063
+ flex-direction: column;
1064
+ gap: 0.5rem;
1065
+ }
1066
+
1067
+ .footer__col h4 {
1068
+ font-size: 0.875rem;
1069
+ font-weight: 600;
1070
+ color: var(--text);
1071
+ margin-bottom: 0.25rem;
1072
+ }
1073
+
1074
+ .footer__col a {
1075
+ font-size: 0.875rem;
1076
+ color: var(--text-muted);
1077
+ text-decoration: none;
1078
+ transition: color 0.15s ease;
1079
+ }
1080
+
1081
+ .footer__col a:hover {
1082
+ color: var(--text);
1083
+ }
1084
+
1085
+ .footer__bottom {
1086
+ padding-top: 2rem;
1087
+ border-top: 1px solid var(--border);
1088
+ text-align: center;
1089
+ }
1090
+
1091
+ .footer__bottom p {
1092
+ font-size: 0.875rem;
1093
+ color: var(--text-subtle);
1094
+ }
1095
+
1096
+ .footer__bottom a {
1097
+ color: var(--text-muted);
1098
+ text-decoration: none;
1099
+ transition: color 0.15s ease;
1100
+ }
1101
+
1102
+ .footer__bottom a:hover {
1103
+ color: var(--text);
1104
+ }
1105
+
1106
+ /* Theme Toggle - Nintendo-inspired */
1107
+ .theme-toggle {
1108
+ --toggle-size: 44px;
1109
+ --icon-size: 20px;
1110
+ position: fixed;
1111
+ top: 1rem;
1112
+ right: 1rem;
1113
+ width: var(--toggle-size);
1114
+ height: var(--toggle-size);
1115
+ display: flex;
1116
+ align-items: center;
1117
+ justify-content: center;
1118
+ background: var(--bg-surface);
1119
+ border: 2px solid var(--border);
1120
+ border-radius: 50%;
1121
+ cursor: pointer;
1122
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
1123
+ overflow: hidden;
1124
+ color: var(--text);
1125
+ }
1126
+
1127
+ .theme-toggle:hover {
1128
+ transform: scale(1.1);
1129
+ border-color: var(--accent);
1130
+ box-shadow: 0 0 20px rgba(203, 166, 247, 0.3);
1131
+ }
1132
+
1133
+ .theme-toggle:active {
1134
+ transform: scale(0.95);
1135
+ }
1136
+
1137
+ .theme-toggle__icons {
1138
+ position: relative;
1139
+ width: var(--icon-size);
1140
+ height: var(--icon-size);
1141
+ }
1142
+
1143
+ .theme-toggle__sun,
1144
+ .theme-toggle__moon,
1145
+ .theme-toggle__auto {
1146
+ position: absolute;
1147
+ inset: 0;
1148
+ display: flex;
1149
+ align-items: center;
1150
+ justify-content: center;
1151
+ transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
1152
+ opacity: 0;
1153
+ transform: rotate(-90deg) scale(0.5);
1154
+ }
1155
+
1156
+ .theme-toggle__sun svg,
1157
+ .theme-toggle__moon svg,
1158
+ .theme-toggle__auto svg {
1159
+ width: var(--icon-size);
1160
+ height: var(--icon-size);
1161
+ }
1162
+
1163
+ .theme-toggle__sun { color: #f9e2af; }
1164
+ .theme-toggle__moon { color: #89b4fa; }
1165
+ .theme-toggle__auto { color: #b4befe; }
1166
+
1167
+ /* Default: show moon */
1168
+ .theme-toggle__moon {
1169
+ opacity: 1;
1170
+ transform: rotate(0deg) scale(1);
1171
+ }
1172
+
1173
+ [data-theme-setting="light"] .theme-toggle__moon {
1174
+ opacity: 0;
1175
+ transform: rotate(-90deg) scale(0.5);
1176
+ }
1177
+ [data-theme-setting="light"] .theme-toggle__sun {
1178
+ opacity: 1;
1179
+ transform: rotate(0deg) scale(1);
1180
+ }
1181
+
1182
+ [data-theme-setting="dark"] .theme-toggle__moon {
1183
+ opacity: 1;
1184
+ transform: rotate(0deg) scale(1);
1185
+ }
1186
+
1187
+ [data-theme-setting="auto"] .theme-toggle__moon {
1188
+ opacity: 0;
1189
+ transform: rotate(-90deg) scale(0.5);
1190
+ }
1191
+ [data-theme-setting="auto"] .theme-toggle__auto {
1192
+ opacity: 1;
1193
+ transform: rotate(0deg) scale(1);
1194
+ }
1195
+
1196
+ .theme-toggle.animating {
1197
+ animation: bounce 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
1198
+ }
1199
+
1200
+ @keyframes bounce {
1201
+ 0%, 100% { transform: scale(1); }
1202
+ 25% { transform: scale(1.15); }
1203
+ 50% { transform: scale(0.9); }
1204
+ 75% { transform: scale(1.05); }
1205
+ }
1206
+
1207
+ @keyframes sparkle-fly {
1208
+ 0% { transform: scale(1); opacity: 1; }
1209
+ 100% { transform: scale(0) translateY(-20px); opacity: 0; }
1210
+ }
1211
+
1212
+ .sr-only {
1213
+ position: absolute;
1214
+ width: 1px;
1215
+ height: 1px;
1216
+ padding: 0;
1217
+ margin: -1px;
1218
+ overflow: hidden;
1219
+ clip: rect(0, 0, 0, 0);
1220
+ white-space: nowrap;
1221
+ border: 0;
1222
+ }
1223
+
1224
+ @media (max-width: 640px) {
1225
+ .hero__title { font-size: 2rem; }
1226
+ .hero__subtitle { font-size: 1.125rem; }
1227
+ .modes { grid-template-columns: 1fr; }
1228
+ .install-widget {
1229
+ flex-direction: column;
1230
+ align-items: stretch;
1231
+ }
1232
+ .install-cmd-wrapper {
1233
+ max-width: 100%;
1234
+ overflow-x: auto;
1235
+ }
1236
+ .install-cmd {
1237
+ font-size: 0.75rem;
1238
+ }
1239
+ }
1240
+ </style>
1241
+ <script>
1242
+ (function() {
1243
+ try {
1244
+ var stored = localStorage.getItem('tanuki-theme') || 'auto';
1245
+ var effective = stored === 'auto'
1246
+ ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
1247
+ : stored;
1248
+ document.documentElement.setAttribute('data-theme', effective);
1249
+ document.documentElement.setAttribute('data-theme-setting', stored);
1250
+ } catch(e) {}
1251
+ })();
1252
+ </script>
1253
+ </head>
1254
+ <body>
1255
+ <!-- Skip Link for Accessibility -->
1256
+ <a href="#main-content" class="skip-link">Skip to main content</a>
1257
+
1258
+ <!-- Theme Toggle - Fixed Position -->
1259
+ <button class="theme-toggle" type="button" aria-label="Toggle color theme: Click to cycle through light, dark, and auto modes" aria-live="polite">
1260
+ <span class="theme-toggle__icons">
1261
+ <span class="theme-toggle__sun" aria-hidden="true">
1262
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1263
+ <circle cx="12" cy="12" r="4"/>
1264
+ <path d="M12 2v2"/>
1265
+ <path d="M12 20v2"/>
1266
+ <path d="m4.93 4.93 1.41 1.41"/>
1267
+ <path d="m17.66 17.66 1.41 1.41"/>
1268
+ <path d="M2 12h2"/>
1269
+ <path d="M20 12h2"/>
1270
+ <path d="m6.34 17.66-1.41 1.41"/>
1271
+ <path d="m19.07 4.93-1.41 1.41"/>
1272
+ </svg>
1273
+ </span>
1274
+ <span class="theme-toggle__moon" aria-hidden="true">
1275
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1276
+ <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>
1277
+ </svg>
1278
+ </span>
1279
+ <span class="theme-toggle__auto" aria-hidden="true">
1280
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1281
+ <circle cx="12" cy="12" r="10"/>
1282
+ <path d="M12 2a7 7 0 1 0 7 7"/>
1283
+ </svg>
1284
+ </span>
1285
+ </span>
1286
+ </button>
1287
+
1288
+ <!-- Main Content -->
1289
+ <main id="main-content" role="main">
1290
+ <!-- Hero Section -->
1291
+ <header class="hero" role="banner" aria-labelledby="hero-title">
1292
+ <div class="container">
1293
+ <div class="hero__logo">
1294
+ <img src="./docs/img/tanuki-icon.avif" alt="Tanuki mascot" width="48" height="48">
1295
+ <span><ruby>Tanuki<rp>(</rp><rt lang="ja">タヌキ</rt><rp>)</rp></ruby></span>
1296
+ </div>
1297
+ <h1 id="hero-title" class="hero__title">An Opinionated Zola Theme</h1>
1298
+ <p class="hero__subtitle">
1299
+ Beautiful, accessible, and thoughtfully designed. Tanuki makes decisions so you can focus on content.
1300
+ </p>
1301
+ <p class="hero__tagline">
1302
+ Soothing <strong>Catppuccin</strong> pastels meet crisp <strong>Geist</strong> typography
1303
+ </p>
1304
+
1305
+ <div class="install-widget">
1306
+ <div class="install-selector">
1307
+ <button class="install-toggle" type="button" onclick="toggleInstallDropdown()" aria-label="Select install method" aria-expanded="false" aria-haspopup="listbox">
1308
+ <span class="install-toggle-icon" id="install-icon" aria-hidden="true">
1309
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1310
+ <rect width="18" height="18" x="3" y="3" rx="2"/>
1311
+ <path d="m10 8 4 4-4 4"/>
1312
+ </svg>
1313
+ </span>
1314
+ <svg class="install-toggle-chevron" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1315
+ <polyline points="6 9 12 15 18 9"/>
1316
+ </svg>
1317
+ </button>
1318
+ <div class="install-dropdown" id="install-dropdown" role="listbox" aria-label="Install methods">
1319
+ <button class="install-option active" type="button" onclick="selectInstall('shell')" data-method="shell" role="option" aria-selected="true">
1320
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1321
+ <rect width="18" height="18" x="3" y="3" rx="2"/>
1322
+ <path d="m10 8 4 4-4 4"/>
1323
+ </svg>
1324
+ Shell
1325
+ </button>
1326
+ </div>
1327
+ </div>
1328
+ <div class="install-cmd-wrapper">
1329
+ <code class="install-cmd" id="install-cmd">git clone https://github.com/raskell-io/tanuki themes/tanuki</code>
1330
+ <button class="copy-btn" id="copy-btn" type="button" aria-label="Copy install command" title="Copy to clipboard">
1331
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1332
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
1333
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
1334
+ </svg>
1335
+ </button>
1336
+ </div>
1337
+ </div>
1338
+ </div>
1339
+ </header>
1340
+
1341
+ <!-- Theme Modes Section -->
1342
+ <section class="section" aria-labelledby="modes-title" itemscope itemtype="https://schema.org/ItemList">
1343
+ <div class="container">
1344
+ <header class="section__header">
1345
+ <h2 id="modes-title" class="section__title" itemprop="name">Four Modes, One Theme</h2>
1346
+ <p class="section__description" itemprop="description">Choose the layout that fits your content</p>
1347
+ </header>
1348
+
1349
+ <div class="modes" role="list" aria-label="Available theme modes">
1350
+ <article class="mode-card mode-card--docs" role="listitem" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
1351
+ <a href="docs/" aria-labelledby="mode-docs-title" itemprop="url">
1352
+ <meta itemprop="position" content="1">
1353
+ <div class="mode-card__header">
1354
+ <div class="mode-card__icon" aria-hidden="true">
1355
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" role="img" aria-hidden="true">
1356
+ <path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"/>
1357
+ <path d="M8 11h8"/>
1358
+ <path d="M8 7h6"/>
1359
+ </svg>
1360
+ </div>
1361
+ <h3 id="mode-docs-title" class="mode-card__title" itemprop="name">Documentation</h3>
1362
+ </div>
1363
+ <p class="mode-card__description" itemprop="description">
1364
+ A resizable sidebar with hierarchical navigation, integrated search, and version picker.
1365
+ Designed for reference material that users need to navigate quickly.
1366
+ </p>
1367
+ <p class="mode-card__use-cases">
1368
+ <strong>Best for:</strong> <span itemprop="keywords">API docs, guides, references, wikis, knowledge bases</span>
1369
+ </p>
1370
+ </a>
1371
+ </article>
1372
+
1373
+ <article class="mode-card mode-card--book" role="listitem" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
1374
+ <a href="book/" aria-labelledby="mode-book-title" itemprop="url">
1375
+ <meta itemprop="position" content="2">
1376
+ <div class="mode-card__header">
1377
+ <div class="mode-card__icon" aria-hidden="true">
1378
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" role="img" aria-hidden="true">
1379
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>
1380
+ <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
1381
+ </svg>
1382
+ </div>
1383
+ <h3 id="mode-book-title" class="mode-card__title" itemprop="name">Book</h3>
1384
+ </div>
1385
+ <p class="mode-card__description" itemprop="description">
1386
+ A distraction-free reading experience with full-screen chapter overlay and keyboard navigation.
1387
+ Maximizes content width for comfortable long-form reading.
1388
+ </p>
1389
+ <p class="mode-card__use-cases">
1390
+ <strong>Best for:</strong> <span itemprop="keywords">Tutorials, e-books, courses, technical books, manuals</span>
1391
+ </p>
1392
+ </a>
1393
+ </article>
1394
+
1395
+ <article class="mode-card mode-card--blog" role="listitem" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
1396
+ <a href="blog/" aria-labelledby="mode-blog-title" itemprop="url">
1397
+ <meta itemprop="position" content="3">
1398
+ <div class="mode-card__header">
1399
+ <div class="mode-card__icon" aria-hidden="true">
1400
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" role="img" aria-hidden="true">
1401
+ <path d="M12 20h9"/>
1402
+ <path d="M16.376 3.622a1 1 0 0 1 3.002 3.002L7.368 18.635a2 2 0 0 1-.855.506l-2.872.838a.5.5 0 0 1-.62-.62l.838-2.872a2 2 0 0 1 .506-.854z"/>
1403
+ </svg>
1404
+ </div>
1405
+ <h3 id="mode-blog-title" class="mode-card__title" itemprop="name">Blog</h3>
1406
+ </div>
1407
+ <p class="mode-card__description" itemprop="description">
1408
+ Card-based listing with tags, dates, and reading time estimates.
1409
+ Individual posts with clean typography optimized for articles.
1410
+ </p>
1411
+ <p class="mode-card__use-cases">
1412
+ <strong>Best for:</strong> <span itemprop="keywords">Personal blogs, dev logs, newsletters, announcements</span>
1413
+ </p>
1414
+ </a>
1415
+ </article>
1416
+
1417
+ <article class="mode-card mode-card--product" role="listitem" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
1418
+ <a href="https://sango.raskell.io" aria-labelledby="mode-product-title" itemprop="url" target="_blank" rel="noopener">
1419
+ <meta itemprop="position" content="4">
1420
+ <div class="mode-card__header">
1421
+ <div class="mode-card__icon" aria-hidden="true">
1422
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" role="img" aria-hidden="true">
1423
+ <path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/>
1424
+ <circle cx="12" cy="12" r="4"/>
1425
+ </svg>
1426
+ </div>
1427
+ <h3 id="mode-product-title" class="mode-card__title" itemprop="name">Product</h3>
1428
+ </div>
1429
+ <p class="mode-card__description" itemprop="description">
1430
+ Full-width landing pages with hero sections, feature grids, install commands, and custom footers.
1431
+ Designed to showcase tools, libraries, and applications.
1432
+ </p>
1433
+ <p class="mode-card__use-cases">
1434
+ <strong>Best for:</strong> <span itemprop="keywords">CLI tools, libraries, SaaS products, open source projects</span>
1435
+ </p>
1436
+ </a>
1437
+ </article>
1438
+ </div>
1439
+ </div>
1440
+ </section>
1441
+
1442
+ <!-- Features Section -->
1443
+ <section class="section section--alt" aria-labelledby="features-title">
1444
+ <div class="container">
1445
+ <header class="section__header">
1446
+ <h2 id="features-title" class="section__title">Opinionated by Design</h2>
1447
+ <p class="section__description">Less configuration, more consistency</p>
1448
+ </header>
1449
+
1450
+ <ul class="features" role="list" aria-label="Design philosophy">
1451
+ <li class="feature" role="listitem">
1452
+ <div class="feature__icon" aria-hidden="true">
1453
+ <!-- Users icon - community -->
1454
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1455
+ <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
1456
+ <circle cx="9" cy="7" r="4"/>
1457
+ <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
1458
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
1459
+ </svg>
1460
+ </div>
1461
+ <div class="feature__content">
1462
+ <h3 class="feature__title">Standing on Giants</h3>
1463
+ <p class="feature__description">Built with battle-tested community favorites: Geist's elegant typography, Lucide's crisp icons, Catppuccin's soothing palette, and elasticlunr's lightning search. Why reinvent what the community has perfected?</p>
1464
+ </div>
1465
+ </li>
1466
+
1467
+ <li class="feature" role="listitem">
1468
+ <div class="feature__icon" aria-hidden="true">
1469
+ <!-- Armchair icon - cozy/Nintendo -->
1470
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1471
+ <path d="M19 9V6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v3"/>
1472
+ <path d="M3 16a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z"/>
1473
+ <path d="M5 18v2"/>
1474
+ <path d="M19 18v2"/>
1475
+ </svg>
1476
+ </div>
1477
+ <div class="feature__content">
1478
+ <h3 class="feature__title">Homey Comfort</h3>
1479
+ <p class="feature__description">Inspired by Nintendo's gift for making complex things feel obvious. Like Animal Crossing's gentle onboarding or Kirby's intuitive controls, everything should feel familiar and welcoming from the first moment.</p>
1480
+ </div>
1481
+ </li>
1482
+
1483
+ <li class="feature" role="listitem">
1484
+ <div class="feature__icon" aria-hidden="true">
1485
+ <!-- Layers icon - minimalism -->
1486
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1487
+ <path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/>
1488
+ <path d="m6.08 9.5-3.5 1.6a1 1 0 0 0 0 1.81l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9a1 1 0 0 0 0-1.83l-3.5-1.59"/>
1489
+ <path d="m6.08 14.5-3.5 1.6a1 1 0 0 0 0 1.81l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9a1 1 0 0 0 0-1.83l-3.5-1.59"/>
1490
+ </svg>
1491
+ </div>
1492
+ <div class="feature__content">
1493
+ <h3 class="feature__title">Graceful Degradation</h3>
1494
+ <p class="feature__description">JavaScript enhances, never gates. Every page works with scripts disabled. Progressive enhancement means content-first: load what matters, enhance when possible. Less is more, even when it's aesthetically delightful.</p>
1495
+ </div>
1496
+ </li>
1497
+
1498
+ <li class="feature" role="listitem">
1499
+ <div class="feature__icon" aria-hidden="true">
1500
+ <!-- Heart icon - human-first -->
1501
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1502
+ <path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7z"/>
1503
+ </svg>
1504
+ </div>
1505
+ <div class="feature__content">
1506
+ <h3 class="feature__title">Humans First</h3>
1507
+ <p class="feature__description">As Matz said when creating Ruby: "I hope to see Ruby help every programmer to be productive, enjoy programming, and be happy." Computers serve humans, not the reverse. Every design decision asks: does this respect the person using it?</p>
1508
+ </div>
1509
+ </li>
1510
+
1511
+ <li class="feature" role="listitem">
1512
+ <div class="feature__icon" aria-hidden="true">
1513
+ <!-- Bot icon - AI/AEO -->
1514
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1515
+ <path d="M12 8V4H8"/>
1516
+ <rect width="16" height="12" x="4" y="8" rx="2"/>
1517
+ <path d="M2 14h2"/>
1518
+ <path d="M20 14h2"/>
1519
+ <path d="M15 13v2"/>
1520
+ <path d="M9 13v2"/>
1521
+ </svg>
1522
+ </div>
1523
+ <div class="feature__content">
1524
+ <h3 class="feature__title">Post-AI Ready</h3>
1525
+ <p class="feature__description">SEO, AEO, and ARIA aren't afterthoughts—they're foundations. JSON-LD structured data, semantic HTML, and accessible markup ensure your content speaks fluently to humans, screen readers, search engines, and AI agents alike.</p>
1526
+ </div>
1527
+ </li>
1528
+
1529
+ <li class="feature" role="listitem">
1530
+ <div class="feature__icon" aria-hidden="true">
1531
+ <!-- Sparkles icon - joy/whimsy -->
1532
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1533
+ <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>
1534
+ <path d="M20 3v4"/>
1535
+ <path d="M22 5h-4"/>
1536
+ <path d="M4 17v2"/>
1537
+ <path d="M5 18H3"/>
1538
+ </svg>
1539
+ </div>
1540
+ <div class="feature__content">
1541
+ <h3 class="feature__title">Whimsical on Purpose</h3>
1542
+ <p class="feature__description">Technology should spark joy, not just function. Serious systems deserve playful interfaces. We refuse to lose the wonder that drew us to building things in the first place. Delight is a feature, not frivolity.</p>
1543
+ </div>
1544
+ </li>
1545
+ </ul>
1546
+ </div>
1547
+ </section>
1548
+
1549
+ <!-- Configuration Section -->
1550
+ <section class="section" aria-labelledby="config-title">
1551
+ <div class="container">
1552
+ <header class="section__header">
1553
+ <h2 id="config-title" class="section__title">Quick Start</h2>
1554
+ <p class="section__description">Copy a config.toml and start building</p>
1555
+ </header>
1556
+
1557
+ <div class="config-tabs" role="tablist" aria-label="Configuration examples">
1558
+ <button class="config-tab config-tab--docs active" role="tab" aria-selected="true" aria-controls="config-docs" id="tab-docs" onclick="switchConfigTab('docs')">
1559
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1560
+ <path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"/>
1561
+ <path d="M8 11h8"/><path d="M8 7h6"/>
1562
+ </svg>
1563
+ Documentation
1564
+ </button>
1565
+ <button class="config-tab config-tab--book" role="tab" aria-selected="false" aria-controls="config-book" id="tab-book" onclick="switchConfigTab('book')">
1566
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1567
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>
1568
+ <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
1569
+ </svg>
1570
+ Book
1571
+ </button>
1572
+ <button class="config-tab config-tab--blog" role="tab" aria-selected="false" aria-controls="config-blog" id="tab-blog" onclick="switchConfigTab('blog')">
1573
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1574
+ <path d="M12 20h9"/>
1575
+ <path d="M16.376 3.622a1 1 0 0 1 3.002 3.002L7.368 18.635a2 2 0 0 1-.855.506l-2.872.838a.5.5 0 0 1-.62-.62l.838-2.872a2 2 0 0 1 .506-.854z"/>
1576
+ </svg>
1577
+ Blog
1578
+ </button>
1579
+ <button class="config-tab config-tab--product" role="tab" aria-selected="false" aria-controls="config-product" id="tab-product" onclick="switchConfigTab('product')">
1580
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1581
+ <path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/>
1582
+ <circle cx="12" cy="12" r="4"/>
1583
+ </svg>
1584
+ Product
1585
+ </button>
1586
+ </div>
1587
+
1588
+ <div class="config-panel active" id="config-docs" role="tabpanel" aria-labelledby="tab-docs">
1589
+ <div class="config-code">
1590
+ <div class="config-code__header">
1591
+ <span class="config-code__filename">config.toml</span>
1592
+ <button class="config-code__copy" onclick="copyConfig('docs')" aria-label="Copy configuration">
1593
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1594
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
1595
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
1596
+ </svg>
1597
+ <span>Copy</span>
1598
+ </button>
1599
+ </div>
1600
+ <pre><code id="config-docs-code"><span class="toml-comment"># Documentation site with sidebar and version picker</span>
1601
+ <span class="toml-key">base_url</span> = <span class="toml-string">"https://docs.example.com"</span>
1602
+ <span class="toml-key">title</span> = <span class="toml-string">"My Project Docs"</span>
1603
+ <span class="toml-key">theme</span> = <span class="toml-string">"tanuki"</span>
1604
+ <span class="toml-key">build_search_index</span> = <span class="toml-bool">true</span>
1605
+
1606
+ <span class="toml-table">[markdown]</span>
1607
+ <span class="toml-key">highlight_code</span> = <span class="toml-bool">true</span>
1608
+ <span class="toml-key">highlight_theme</span> = <span class="toml-string">"css"</span>
1609
+
1610
+ <span class="toml-table">[extra]</span>
1611
+ <span class="toml-key">mode</span> = <span class="toml-string">"docs"</span>
1612
+ <span class="toml-key">github</span> = <span class="toml-string">"https://github.com/you/project"</span>
1613
+
1614
+ <span class="toml-comment"># Optional: version picker</span>
1615
+ <span class="toml-table">[extra.versions]</span>
1616
+ <span class="toml-key">current</span> = <span class="toml-string">"2.0.0"</span>
1617
+ <span class="toml-key">list</span> = [
1618
+ { <span class="toml-key">version</span> = <span class="toml-string">"2.0.0"</span>, <span class="toml-key">url</span> = <span class="toml-string">"/"</span>, <span class="toml-key">label</span> = <span class="toml-string">"latest"</span> },
1619
+ { <span class="toml-key">version</span> = <span class="toml-string">"1.0.0"</span>, <span class="toml-key">url</span> = <span class="toml-string">"/v1/"</span> },
1620
+ ]</code></pre>
1621
+ </div>
1622
+ </div>
1623
+
1624
+ <div class="config-panel" id="config-book" role="tabpanel" aria-labelledby="tab-book">
1625
+ <div class="config-code">
1626
+ <div class="config-code__header">
1627
+ <span class="config-code__filename">config.toml</span>
1628
+ <button class="config-code__copy" onclick="copyConfig('book')" aria-label="Copy configuration">
1629
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1630
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
1631
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
1632
+ </svg>
1633
+ <span>Copy</span>
1634
+ </button>
1635
+ </div>
1636
+ <pre><code id="config-book-code"><span class="toml-comment"># E-book or tutorial with chapter navigation</span>
1637
+ <span class="toml-key">base_url</span> = <span class="toml-string">"https://book.example.com"</span>
1638
+ <span class="toml-key">title</span> = <span class="toml-string">"The Complete Guide"</span>
1639
+ <span class="toml-key">theme</span> = <span class="toml-string">"tanuki"</span>
1640
+ <span class="toml-key">build_search_index</span> = <span class="toml-bool">true</span>
1641
+
1642
+ <span class="toml-table">[markdown]</span>
1643
+ <span class="toml-key">highlight_code</span> = <span class="toml-bool">true</span>
1644
+ <span class="toml-key">highlight_theme</span> = <span class="toml-string">"css"</span>
1645
+
1646
+ <span class="toml-table">[extra]</span>
1647
+ <span class="toml-key">mode</span> = <span class="toml-string">"book"</span>
1648
+ <span class="toml-key">github</span> = <span class="toml-string">"https://github.com/you/book"</span></code></pre>
1649
+ </div>
1650
+ </div>
1651
+
1652
+ <div class="config-panel" id="config-blog" role="tabpanel" aria-labelledby="tab-blog">
1653
+ <div class="config-code">
1654
+ <div class="config-code__header">
1655
+ <span class="config-code__filename">config.toml</span>
1656
+ <button class="config-code__copy" onclick="copyConfig('blog')" aria-label="Copy configuration">
1657
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1658
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
1659
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
1660
+ </svg>
1661
+ <span>Copy</span>
1662
+ </button>
1663
+ </div>
1664
+ <pre><code id="config-blog-code"><span class="toml-comment"># Personal blog with RSS feed and tags</span>
1665
+ <span class="toml-key">base_url</span> = <span class="toml-string">"https://blog.example.com"</span>
1666
+ <span class="toml-key">title</span> = <span class="toml-string">"My Blog"</span>
1667
+ <span class="toml-key">theme</span> = <span class="toml-string">"tanuki"</span>
1668
+ <span class="toml-key">generate_feeds</span> = <span class="toml-bool">true</span>
1669
+
1670
+ <span class="toml-key">taxonomies</span> = [
1671
+ { <span class="toml-key">name</span> = <span class="toml-string">"tags"</span>, <span class="toml-key">feed</span> = <span class="toml-bool">true</span> },
1672
+ ]
1673
+
1674
+ <span class="toml-table">[markdown]</span>
1675
+ <span class="toml-key">highlight_code</span> = <span class="toml-bool">true</span>
1676
+ <span class="toml-key">highlight_theme</span> = <span class="toml-string">"css"</span>
1677
+
1678
+ <span class="toml-table">[extra]</span>
1679
+ <span class="toml-key">mode</span> = <span class="toml-string">"blog"</span>
1680
+
1681
+ <span class="toml-comment"># Optional: hero section</span>
1682
+ <span class="toml-table">[extra.hero]</span>
1683
+ <span class="toml-key">title</span> = <span class="toml-string">"Welcome to my blog"</span>
1684
+ <span class="toml-key">subtitle</span> = <span class="toml-string">"Thoughts on code and craft"</span>
1685
+
1686
+ <span class="toml-comment"># Optional: navigation</span>
1687
+ <span class="toml-array-table">[[extra.nav]]</span>
1688
+ <span class="toml-key">name</span> = <span class="toml-string">"Blog"</span>
1689
+ <span class="toml-key">url</span> = <span class="toml-string">"/blog/"</span>
1690
+
1691
+ <span class="toml-array-table">[[extra.nav]]</span>
1692
+ <span class="toml-key">name</span> = <span class="toml-string">"About"</span>
1693
+ <span class="toml-key">url</span> = <span class="toml-string">"/about/"</span></code></pre>
1694
+ </div>
1695
+ </div>
1696
+
1697
+ <div class="config-panel" id="config-product" role="tabpanel" aria-labelledby="tab-product">
1698
+ <div class="config-code">
1699
+ <div class="config-code__header">
1700
+ <span class="config-code__filename">config.toml</span>
1701
+ <button class="config-code__copy" onclick="copyConfig('product')" aria-label="Copy configuration">
1702
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1703
+ <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
1704
+ <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
1705
+ </svg>
1706
+ <span>Copy</span>
1707
+ </button>
1708
+ </div>
1709
+ <pre><code id="config-product-code"><span class="toml-comment"># Product landing page with features and install</span>
1710
+ <span class="toml-key">base_url</span> = <span class="toml-string">"https://myproduct.example.com"</span>
1711
+ <span class="toml-key">title</span> = <span class="toml-string">"My Product"</span>
1712
+ <span class="toml-key">description</span> = <span class="toml-string">"A powerful tool for developers"</span>
1713
+ <span class="toml-key">theme</span> = <span class="toml-string">"tanuki"</span>
1714
+
1715
+ <span class="toml-table">[markdown]</span>
1716
+ <span class="toml-key">highlight_code</span> = <span class="toml-bool">true</span>
1717
+ <span class="toml-key">highlight_theme</span> = <span class="toml-string">"css"</span>
1718
+
1719
+ <span class="toml-table">[extra]</span>
1720
+ <span class="toml-key">mode</span> = <span class="toml-string">"product"</span>
1721
+ <span class="toml-key">github</span> = <span class="toml-string">"https://github.com/you/project"</span>
1722
+
1723
+ <span class="toml-comment"># Product-specific settings</span>
1724
+ <span class="toml-table">[extra.product]</span>
1725
+ <span class="toml-key">icon</span> = <span class="toml-string">"assets/product-icon.png"</span>
1726
+ <span class="toml-key">tagline</span> = <span class="toml-string">"Built for modern developers"</span>
1727
+ <span class="toml-key">install_command</span> = <span class="toml-string">"curl -fsSL get.example.com | sh"</span>
1728
+
1729
+ <span class="toml-comment"># Navigation</span>
1730
+ <span class="toml-array-table">[[extra.nav]]</span>
1731
+ <span class="toml-key">name</span> = <span class="toml-string">"Features"</span>
1732
+ <span class="toml-key">url</span> = <span class="toml-string">"#features"</span>
1733
+
1734
+ <span class="toml-array-table">[[extra.nav]]</span>
1735
+ <span class="toml-key">name</span> = <span class="toml-string">"Install"</span>
1736
+ <span class="toml-key">url</span> = <span class="toml-string">"#install"</span></code></pre>
1737
+ </div>
1738
+ </div>
1739
+ </div>
1740
+ </section>
1741
+ </main>
1742
+
1743
+ <!-- Footer -->
1744
+ <footer class="footer" role="contentinfo" itemscope itemtype="https://schema.org/WPFooter">
1745
+ <div class="container">
1746
+ <div class="footer__content">
1747
+ <div class="footer__brand" itemscope itemtype="https://schema.org/Organization">
1748
+ <div class="footer__logo-wrapper" aria-hidden="true">
1749
+ <div class="footer__logo-glow"></div>
1750
+ <img class="footer__logo" src="./docs/img/tanuki-icon.avif" alt="Tanuki logo" width="64" height="64">
1751
+ </div>
1752
+ <div class="footer__tagline">
1753
+ <p class="footer__title" itemprop="name">An opinionated Zola theme<br>for docs, books, blogs, and products</p>
1754
+ <p class="footer__description" itemprop="description">Beautiful, accessible, and thoughtfully designed. Tanuki makes decisions so you can focus on content.</p>
1755
+ </div>
1756
+ </div>
1757
+ <nav class="footer__links" aria-label="Footer navigation">
1758
+ <div class="footer__col" role="navigation" aria-labelledby="footer-resources">
1759
+ <h4 id="footer-resources">Resources</h4>
1760
+ <a href="https://github.com/raskell-io/tanuki" target="_blank" rel="noopener noreferrer" aria-label="View Tanuki on GitHub (opens in new tab)">GitHub</a>
1761
+ <a href="https://www.getzola.org/" target="_blank" rel="noopener noreferrer" aria-label="Visit Zola static site generator (opens in new tab)">Zola</a>
1762
+ <a href="docs/" aria-label="Read the Tanuki documentation">Documentation</a>
1763
+ <a href="https://github.com/raskell-io/tanuki/blob/main/LICENSE" target="_blank" rel="noopener noreferrer" aria-label="View MIT License (opens in new tab)">MIT License</a>
1764
+ </div>
1765
+ <div class="footer__col" role="navigation" aria-labelledby="footer-inspiration">
1766
+ <h4 id="footer-inspiration">Inspiration</h4>
1767
+ <a href="https://catppuccin.com/" target="_blank" rel="noopener noreferrer" aria-label="Visit Catppuccin color palette (opens in new tab)">Catppuccin</a>
1768
+ <a href="https://vercel.com/font" target="_blank" rel="noopener noreferrer" aria-label="Visit Geist Font by Vercel (opens in new tab)">Geist Font</a>
1769
+ <a href="https://lucide.dev/" target="_blank" rel="noopener noreferrer" aria-label="Visit Lucide Icons (opens in new tab)">Lucide Icons</a>
1770
+ </div>
1771
+ </nav>
1772
+ </div>
1773
+ <div class="footer__bottom">
1774
+ <p>Made with care for readers and writers alike. <a href="https://raskell.io" target="_blank" rel="noopener noreferrer" aria-label="Visit raskell.io (opens in new tab)">raskell.io</a></p>
1775
+ </div>
1776
+ </div>
1777
+ </footer>
1778
+
1779
+ <script>
1780
+ (function() {
1781
+ const THEMES = ['light', 'dark', 'auto'];
1782
+ const button = document.querySelector('.theme-toggle');
1783
+
1784
+ function getStoredTheme() {
1785
+ try { return localStorage.getItem('tanuki-theme') || 'auto'; }
1786
+ catch(e) { return 'auto'; }
1787
+ }
1788
+
1789
+ function getEffectiveTheme(stored) {
1790
+ if (stored === 'auto') {
1791
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
1792
+ }
1793
+ return stored;
1794
+ }
1795
+
1796
+ function applyTheme(theme) {
1797
+ const effective = getEffectiveTheme(theme);
1798
+ document.documentElement.setAttribute('data-theme', effective);
1799
+ document.documentElement.setAttribute('data-theme-setting', theme);
1800
+ }
1801
+
1802
+ function cycleTheme() {
1803
+ const current = getStoredTheme();
1804
+ const nextIndex = (THEMES.indexOf(current) + 1) % THEMES.length;
1805
+ return THEMES[nextIndex];
1806
+ }
1807
+
1808
+ function createSparkles() {
1809
+ const rect = button.getBoundingClientRect();
1810
+ const centerX = rect.left + rect.width / 2;
1811
+ const centerY = rect.top + rect.height / 2;
1812
+
1813
+ for (let i = 0; i < 6; i++) {
1814
+ const sparkle = document.createElement('div');
1815
+ const angle = (i / 6) * Math.PI * 2;
1816
+ const distance = 30 + Math.random() * 20;
1817
+ const x = centerX + Math.cos(angle) * distance;
1818
+ const y = centerY + Math.sin(angle) * distance;
1819
+
1820
+ sparkle.style.cssText = `
1821
+ position: fixed;
1822
+ left: ${x}px;
1823
+ top: ${y}px;
1824
+ width: 6px;
1825
+ height: 6px;
1826
+ background: #f9e2af;
1827
+ border-radius: 50%;
1828
+ pointer-events: none;
1829
+ z-index: 10000;
1830
+ animation: sparkle-fly 0.6s ease-out forwards;
1831
+ `;
1832
+
1833
+ document.body.appendChild(sparkle);
1834
+ setTimeout(() => sparkle.remove(), 600);
1835
+ }
1836
+ }
1837
+
1838
+ button.addEventListener('click', () => {
1839
+ const newTheme = cycleTheme();
1840
+ localStorage.setItem('tanuki-theme', newTheme);
1841
+ applyTheme(newTheme);
1842
+
1843
+ button.classList.add('animating');
1844
+ setTimeout(() => button.classList.remove('animating'), 500);
1845
+
1846
+ createSparkles();
1847
+ });
1848
+
1849
+ // Listen for system theme changes
1850
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
1851
+ if (getStoredTheme() === 'auto') {
1852
+ applyTheme('auto');
1853
+ }
1854
+ });
1855
+ })();
1856
+
1857
+ // Install widget functionality
1858
+ (function() {
1859
+ const installCommands = {
1860
+ shell: 'git clone https://github.com/raskell-io/tanuki themes/tanuki'
1861
+ };
1862
+
1863
+ const installIcons = {
1864
+ shell: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="m10 8 4 4-4 4"/></svg>'
1865
+ };
1866
+
1867
+ let currentMethod = 'shell';
1868
+ const copyBtn = document.getElementById('copy-btn');
1869
+ const installCmdEl = document.getElementById('install-cmd');
1870
+ const installIconEl = document.getElementById('install-icon');
1871
+ const installDropdown = document.getElementById('install-dropdown');
1872
+ const installToggle = document.querySelector('.install-toggle');
1873
+
1874
+ const copyIconHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>';
1875
+ const checkIconHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
1876
+ let copyTimeout = null;
1877
+
1878
+ // Dropdown toggle
1879
+ window.toggleInstallDropdown = function() {
1880
+ const isOpen = installDropdown.classList.toggle('open');
1881
+ installToggle.setAttribute('aria-expanded', isOpen);
1882
+ };
1883
+
1884
+ // Select install method
1885
+ window.selectInstall = function(method) {
1886
+ currentMethod = method;
1887
+
1888
+ // Reset copy button
1889
+ if (copyTimeout) {
1890
+ clearTimeout(copyTimeout);
1891
+ copyTimeout = null;
1892
+ }
1893
+ copyBtn.innerHTML = copyIconHTML;
1894
+ copyBtn.classList.remove('copied');
1895
+
1896
+ // Update command
1897
+ installCmdEl.textContent = installCommands[method];
1898
+
1899
+ // Update toggle icon
1900
+ installIconEl.innerHTML = installIcons[method];
1901
+
1902
+ // Update active state
1903
+ document.querySelectorAll('.install-option').forEach(opt => {
1904
+ const isActive = opt.dataset.method === method;
1905
+ opt.classList.toggle('active', isActive);
1906
+ opt.setAttribute('aria-selected', isActive);
1907
+ });
1908
+
1909
+ // Close dropdown
1910
+ installDropdown.classList.remove('open');
1911
+ installToggle.setAttribute('aria-expanded', 'false');
1912
+ };
1913
+
1914
+ // Confetti effect
1915
+ function createConfetti(originX, originY) {
1916
+ const colors = ['#cba6f7', '#fab387', '#b4befe', '#f5c2e7', '#94e2d5'];
1917
+
1918
+ for (let i = 0; i < 10; i++) {
1919
+ const particle = document.createElement('div');
1920
+ const angle = (Math.PI * 2 * i) / 10;
1921
+ const dist = 35 + Math.random() * 25;
1922
+
1923
+ particle.style.cssText = `
1924
+ position: fixed;
1925
+ left: ${originX}px;
1926
+ top: ${originY}px;
1927
+ width: 5px;
1928
+ height: 5px;
1929
+ background: ${colors[i % colors.length]};
1930
+ border-radius: 50%;
1931
+ pointer-events: none;
1932
+ z-index: 9999;
1933
+ --tx: ${Math.cos(angle) * dist}px;
1934
+ --ty: ${Math.sin(angle) * dist + 15}px;
1935
+ animation: confetti-burst 0.45s ease-out forwards;
1936
+ `;
1937
+
1938
+ document.body.appendChild(particle);
1939
+ setTimeout(() => particle.remove(), 450);
1940
+ }
1941
+ }
1942
+
1943
+ // Copy button click
1944
+ if (copyBtn) {
1945
+ copyBtn.addEventListener('click', function() {
1946
+ const rect = copyBtn.getBoundingClientRect();
1947
+ const x = rect.left + rect.width / 2;
1948
+ const y = rect.top + rect.height / 2;
1949
+
1950
+ navigator.clipboard.writeText(installCommands[currentMethod]).then(() => {
1951
+ copyBtn.innerHTML = checkIconHTML;
1952
+ copyBtn.classList.add('copied');
1953
+ createConfetti(x, y);
1954
+
1955
+ if (copyTimeout) clearTimeout(copyTimeout);
1956
+ copyTimeout = setTimeout(() => {
1957
+ copyBtn.innerHTML = copyIconHTML;
1958
+ copyBtn.classList.remove('copied');
1959
+ copyTimeout = null;
1960
+ }, 2000);
1961
+ });
1962
+ });
1963
+ }
1964
+
1965
+ // Close dropdown when clicking outside
1966
+ document.addEventListener('click', function(e) {
1967
+ if (!installDropdown.contains(e.target) && !installToggle.contains(e.target)) {
1968
+ installDropdown.classList.remove('open');
1969
+ installToggle.setAttribute('aria-expanded', 'false');
1970
+ }
1971
+ });
1972
+ })();
1973
+
1974
+ // Config tab switching
1975
+ (function() {
1976
+ const configs = {
1977
+ docs: `# Documentation site with sidebar and version picker
1978
+ base_url = "https://docs.example.com"
1979
+ title = "My Project Docs"
1980
+ theme = "tanuki"
1981
+ build_search_index = true
1982
+
1983
+ [markdown]
1984
+ highlight_code = true
1985
+ highlight_theme = "css"
1986
+
1987
+ [extra]
1988
+ mode = "docs"
1989
+ github = "https://github.com/you/project"
1990
+
1991
+ # Optional: version picker
1992
+ [extra.versions]
1993
+ current = "2.0.0"
1994
+ list = [
1995
+ { version = "2.0.0", url = "/", label = "latest" },
1996
+ { version = "1.0.0", url = "/v1/" },
1997
+ ]`,
1998
+ book: `# E-book or tutorial with chapter navigation
1999
+ base_url = "https://book.example.com"
2000
+ title = "The Complete Guide"
2001
+ theme = "tanuki"
2002
+ build_search_index = true
2003
+
2004
+ [markdown]
2005
+ highlight_code = true
2006
+ highlight_theme = "css"
2007
+
2008
+ [extra]
2009
+ mode = "book"
2010
+ github = "https://github.com/you/book"`,
2011
+ blog: `# Personal blog with RSS feed and tags
2012
+ base_url = "https://blog.example.com"
2013
+ title = "My Blog"
2014
+ theme = "tanuki"
2015
+ generate_feeds = true
2016
+
2017
+ taxonomies = [
2018
+ { name = "tags", feed = true },
2019
+ ]
2020
+
2021
+ [markdown]
2022
+ highlight_code = true
2023
+ highlight_theme = "css"
2024
+
2025
+ [extra]
2026
+ mode = "blog"
2027
+
2028
+ # Optional: hero section
2029
+ [extra.hero]
2030
+ title = "Welcome to my blog"
2031
+ subtitle = "Thoughts on code and craft"
2032
+
2033
+ # Optional: navigation
2034
+ [[extra.nav]]
2035
+ name = "Blog"
2036
+ url = "/blog/"
2037
+
2038
+ [[extra.nav]]
2039
+ name = "About"
2040
+ url = "/about/"`
2041
+ };
2042
+
2043
+ window.switchConfigTab = function(mode) {
2044
+ // Update tabs
2045
+ document.querySelectorAll('.config-tab').forEach(tab => {
2046
+ const isActive = tab.id === `tab-${mode}`;
2047
+ tab.classList.toggle('active', isActive);
2048
+ tab.setAttribute('aria-selected', isActive);
2049
+ });
2050
+
2051
+ // Update panels
2052
+ document.querySelectorAll('.config-panel').forEach(panel => {
2053
+ panel.classList.toggle('active', panel.id === `config-${mode}`);
2054
+ });
2055
+ };
2056
+
2057
+ window.copyConfig = function(mode) {
2058
+ const btn = document.querySelector(`#config-${mode} .config-code__copy`);
2059
+ const text = configs[mode];
2060
+
2061
+ navigator.clipboard.writeText(text).then(() => {
2062
+ btn.classList.add('copied');
2063
+ btn.querySelector('span').textContent = 'Copied!';
2064
+
2065
+ // Confetti
2066
+ const rect = btn.getBoundingClientRect();
2067
+ const x = rect.left + rect.width / 2;
2068
+ const y = rect.top + rect.height / 2;
2069
+ const colors = ['#cba6f7', '#fab387', '#b4befe', '#f5c2e7', '#94e2d5'];
2070
+
2071
+ for (let i = 0; i < 8; i++) {
2072
+ const particle = document.createElement('div');
2073
+ const angle = (Math.PI * 2 * i) / 8;
2074
+ const dist = 30 + Math.random() * 20;
2075
+
2076
+ particle.style.cssText = `
2077
+ position: fixed;
2078
+ left: ${x}px;
2079
+ top: ${y}px;
2080
+ width: 5px;
2081
+ height: 5px;
2082
+ background: ${colors[i % colors.length]};
2083
+ border-radius: 50%;
2084
+ pointer-events: none;
2085
+ z-index: 9999;
2086
+ --tx: ${Math.cos(angle) * dist}px;
2087
+ --ty: ${Math.sin(angle) * dist + 15}px;
2088
+ animation: confetti-burst 0.45s ease-out forwards;
2089
+ `;
2090
+
2091
+ document.body.appendChild(particle);
2092
+ setTimeout(() => particle.remove(), 450);
2093
+ }
2094
+
2095
+ setTimeout(() => {
2096
+ btn.classList.remove('copied');
2097
+ btn.querySelector('span').textContent = 'Copy';
2098
+ }, 2000);
2099
+ });
2100
+ };
2101
+ })();
2102
+ </script>
2103
+ </body>
2104
+ </html>