feat: add theme engine + standardize params.yaml

Re-implements theme engine in Tailwind from the previous Bootstrap version.

Re-organizes appearance params in `params.yaml`.

Also,
- edit Readme
- add popular front matter alias `description` for `summary`
  - Created a new helper function: get_summary.html
  - Implements the priority order: .Params.summary > .Params.description > .Params.abstract > .Summary.
- clamp abstract in date-title-summary view
- default gradient mesh block bg to primary-secondary gradient from theme pack
- improve inline code styling - remove backticks around code

BREAKING CHANGE:
`params.yaml` data structure has changed - see docs and latest templates for example
This commit is contained in:
George Cushen committed 2025-11-30 04:45:38 +00:00
1 parent 51efdcd098
commit d1e63984e6
61 files changed
+2620 -530

No files matched your search

+1
View File
@@ -40,6 +40,7 @@ starters/**/pnpm-lock.yaml
.idea/
.vscode/
.specstory/
docs/
# ============================================================================
# Language & Framework Specific
+12 -11
View File
@@ -2,7 +2,7 @@
<p align="center">
<a href="https://hugoblox.com/start?utm_source=github&utm_medium=readme">
<img src="./.github/media/hero.png" alt="Build world-class sites with Hugo Blox" width="100%">
<img src="./.github/media/hero.png" alt="Build world-class sites with HugoBlox" width="100%">
</a>
</p>
@@ -33,11 +33,11 @@
---
## Why Top Talent Chooses Hugo Blox
## Why Top Talent Chooses HugoBlox
In the age of AI agents, **Markdown is the new source code**.
Vibe coding tools generate fragile, "black box" React/JS apps that are a nightmare to maintain once the AI disconnects. **Hugo Blox** is different. We combine the speed of an AI builder with the robustness of a static site generator.
Vibe coding tools generate fragile, "black box" React/JS apps that are a nightmare to maintain once the AI disconnects. **HugoBlox** is different. We combine the speed of an AI builder with the robustness of a static site generator.
- **🔮 Future-Proof & AI Ready:** Your content lives in clean Markdown—the native language of LLMs. It's readable by humans and machines forever. No vendor lock-in.
- **🧠 The "Copilot" Workflow:**
@@ -80,16 +80,17 @@ For more guides, visit our documentation at [**https://docs.hugoblox.com/**](htt
## Free vs. Pro
**Hugo Blox** is open core. You can build a world-class site for free. **Pro** is for those who want to move faster and support the ecosystem.
**HugoBlox** is open core. You can build a world-class site for free. **Pro** is for those who want to move faster and support the ecosystem.
| Feature | Open Source | **Hugo Blox Pro** |
| Feature | Open Source | **HugoBlox Pro** |
| :--- | :---: | :---: |
| **Core Framework** | ✅ | ✅ |
| **Standard Blocks** (Hero, Features, etc.) | ✅ | ✅ |
| **Premium Templates** (SaaS, Course, Lab) | ❌ | **✅ Included** |
| **Studio site editor** | ❌ | **✅ Included** |
| **Premium Templates** (SaaS, Lab) | ❌ | **✅ Included** |
| **Exclusive Pro Blocks** | ❌ | **✅ Included** |
| **Lifetime Updates** | ❌ | **✅ Included** |
| **Support the Project** | ❤️ | **🏆 Hero Status** |
| **Remove HugoBlox attribution?** | ❌ | **✅ Included** |
| **Discord Community Support** | ❤️ | **⚡️ Priority** |
| **Support Open Source/Research** | ❤️ | **🏆 Hero Status** |
👉 [**Upgrade to Pro**](https://hugoblox.com/pro?utm_source=github&utm_medium=readme)
@@ -97,7 +98,7 @@ For more guides, visit our documentation at [**https://docs.hugoblox.com/**](htt
## What Creators Say
> "Hugo Blox saved me 40+ hours on my lab site. Bibtex integration auto-updates publications — **our citations are up 3×**."
> "HugoBlox saved me 40+ hours on my lab site. Bibtex integration auto-updates publications — **our citations are up 3×**."
> — **Dr. Sarah Yang**, AI Researcher
> "We launched our documentation in 10 minutes. The block system is genius; **onboarding time dropped by 60%**."
@@ -107,7 +108,7 @@ For more guides, visit our documentation at [**https://docs.hugoblox.com/**](htt
## Support the Project
- 💎 **Pro (one-time donation)** — Get lifetime access to all premium templates, blocks, and updates.
- 💎 **Pro (only $8/m)** — Get access to the visual editor and all premium templates, blocks, and updates.
<br/>[**Go Pro →**](https://hugoblox.com/pro?utm_source=github&utm_medium=readme)
- ☕️ **Donate a Coffee** — Support open source and open research.
+18 -17
View File
@@ -2,7 +2,7 @@
<p align="center">
<a href="https://hugoblox.com/start?utm_source=github&utm_medium=readme">
<img src="./.github/media/zh-hero.png" alt="用 Hugo Blox 打造世界级站点" width="100%">
<img src="./.github/media/zh-hero.png" alt="用 HugoBlox 打造世界级站点" width="100%">
</a>
</p>
@@ -16,7 +16,7 @@
<p align="center">
<a href="https://hugoblox.com/start?utm_source=github&utm_medium=readme"><b>🚀 启动 Copilot(免费)</b></a>
&nbsp;•&nbsp;
<a href="https://hugoblox.com/pro?utm_source=github&utm_medium=readme"><b>升级 Pro(终身)</b></a>
<a href="https://hugoblox.com/pro?utm_source=github&utm_medium=readme"><b>升级 Pro</b></a>
</p>
<p align="center">
@@ -24,7 +24,7 @@
<img src="https://img.shields.io/discord/722225264733716590?label=%E5%8A%A0%E5%85%A5%20Discord&style=social" alt="Discord">
</a>
<a href="https://github.com/HugoBlox/hugo-blox-builder">
<img src="https://img.shields.io/github/stars/HugoBlox/hugo-blox-builder?label=%E4%B8%BA%20Hugo%20Blox%20%E5%8A%A0%E6%98%9F&style=social" alt="为 Hugo Blox 加星">
<img src="https://img.shields.io/github/stars/HugoBlox/hugo-blox-builder?label=%E4%B8%BA%20Hugo%20Blox%20%E5%8A%A0%E6%98%9F&style=social" alt="为 HugoBlox 加星">
</a>
<a href="https://github.com/gcushen">
<img src="https://img.shields.io/github/followers/gcushen?label=%E5%85%B3%E6%B3%A8&style=social" alt="在 GitHub 关注">
@@ -33,11 +33,11 @@
---
## 为什么顶尖开发者和研究者选择 Hugo Blox?
## 为什么顶尖开发者和研究者选择 HugoBlox?
在智能体 / Copilot 时代,**Markdown 正在变成新的「源码」**。
市面上各种 vibe coding / 生成式建站工具,会一股脑吐出一大坨难以维护的 React/JS 黑盒代码,一旦离开那个 AI,很快就失控。**Hugo Blox 选的是另一条路:用极简的静态站栈,配上开源 Copilot 体验。**
市面上各种 vibe coding / 生成式建站工具,会一股脑吐出一大坨难以维护的 React/JS 黑盒代码,一旦离开那个 AI,很快就失控。**HugoBlox 选的是另一条路:用极简的静态站栈,配上开源 Copilot 体验。**
- **🔮 面向未来的内容资产:** 内容以干净的 Markdown 存储——这是 LLM 和人类都读得懂的格式,迁移简单、长期可用,没有厂商锁定。
- **🧠 Copilot 工作流:**
@@ -78,24 +78,25 @@ npx hugoblox create
## 开源版 vs Pro 版
Hugo Blox 是 **开源内核 + Pro 增强** 的模式。你可以用开源版搭建一流站点;如果你希望更快推进项目、同时支持生态发展,可以考虑升级 Pro。
HugoBlox 是 **开源内核 + Pro 增强** 的模式。你可以用开源版搭建一流站点;如果你希望更快推进项目、同时支持生态发展,可以考虑升级 Pro。
| 能力 | 开源版 | **Hugo Blox Pro** |
| 能力 | 开源版 | **HugoBlox Pro** |
| :--- | :---: | :---: |
| **核心框架** | ✅ | ✅ |
| **标准积木**(Hero、功能介绍等) | ✅ | ✅ |
| **高级整站模板**(SaaS、课程、实验室主页等) | ❌ | **✅ 全部解锁** |
| **独家 Pro 积木** | ❌ | **✅ 全部解锁** |
| **终身更新** | ❌ | **✅ 包含** |
| **支持项目 / 赞助开源** | ❤️ | **🏆 真爱用户** |
| **Studio 站点编辑器** | ❌ | **✅ 包含** |
| **高级整站模板**(SaaS、实验室主页等) | ❌ | **✅ 包含** |
| **独家 Pro 积木** | ❌ | **✅ 包含** |
| **移除 HugoBlox 版权声明?** | ❌ | **✅ 包含** |
| **Discord 社区支持** | ❤️ | **⚡️ 优先支持** |
| **支持开源 / 科研** | ❤️ | **🏆 Hero 身份** |
👉 [**升级 Pro(终身)**](https://hugoblox.com/pro?utm_source=github&utm_medium=readme)
👉 [**升级 Pro**](https://hugoblox.com/pro?utm_source=github&utm_medium=readme)
---
## 创作者怎么说
> “Hugo Blox 直接帮我省了 40+ 小时做实验室网站。BibTeX 自动同步出版物 —— **引用量提升了 3×**。”
> “HugoBlox 直接帮我省了 40+ 小时做实验室网站。BibTeX 自动同步出版物 —— **引用量提升了 3×**。”
> —— **Sarah Yang 博士**,AI 研究者
> “我们 10 分钟就把文档站点上线了。积木系统太妙了;**新同学上手时间缩短了 60%**。”
@@ -105,8 +106,8 @@ Hugo Blox 是 **开源内核 + Pro 增强** 的模式。你可以用开源版搭
## 支持这个项目
- 💎 **Pro(一次性捐赠)** — 终身获取全部高级模板、积木与更新。
<br/>[**立即升级 Pro(终身)→**](https://hugoblox.com/pro?utm_source=github&utm_medium=readme)
- 💎 **Pro(仅需 $8/月)** — 获取可视化编辑器以及所有高级模板、积木与更新。
<br/>[**立即升级 Pro →**](https://hugoblox.com/pro?utm_source=github&utm_medium=readme)
- ☕️ **请我们喝杯咖啡** — 支持开源与开放科研。
<br/>[**GitHub 赞助 →**](https://github.com/sponsors/gcushen)
@@ -123,7 +124,7 @@ Hugo Blox 是 **开源内核 + Pro 增强** 的模式。你可以用开源版搭
- 💬 [Discord](https://discord.gg/z8wNYzb)
- 📚 [文档与指南](https://docs.hugoblox.com/?utm_source=github&utm_medium=readme)
- 🐦 [X / Twitter](https://x.com/BuildLore)
- ⭐ [为 Hugo Blox 加星](https://github.com/HugoBlox/hugo-blox-builder)
- ⭐ [为 HugoBlox 加星](https://github.com/HugoBlox/hugo-blox-builder)
---
+7 -4
View File
@@ -13,21 +13,24 @@
"**/node_modules/**": true,
"**/public/**": true,
"**/resources/**": true,
"**/pagefind/**": true
"**/pagefind/**": true,
"**/dist/**": true
},
"search.exclude": {
"**/node_modules/**": true,
"**/public/**": true,
"**/resources/**": true,
"**/pagefind/**": true
"**/pagefind/**": true,
"**/dist/**": true
},
"files.watcherExclude": {
"**/node_modules/**": true,
"**/public/**": true,
"**/resources/**": true,
"**/pagefind/**": true
"**/pagefind/**": true,
"**/dist/**": true
},
"explorer.excludeGitIgnore": true,
"explorer.excludeGitIgnore": false,
"workbench.editor.labelFormat": "medium",
"breadcrumbs.enabled": true,
"npm.packageManager": "pnpm",
@@ -6,5 +6,6 @@
@import "steps.css";
@import "cards.css";
@import "math.css";
@import "notebook.css";
@import "author-notes.css";
@import "glassmorphism.css";
@@ -0,0 +1,150 @@
.hb-notebook {
--hb-notebook-output-max-height: 26rem;
@apply relative isolate my-10 rounded-3xl border border-zinc-200/70 bg-white/90 p-6 shadow-xl shadow-primary-500/5 transition-shadow duration-300;
@apply dark:border-zinc-800/70 dark:bg-zinc-900/70 dark:shadow-black/30 text-zinc-800 dark:text-zinc-50;
}
.hb-notebook::after {
content: "";
@apply pointer-events-none absolute inset-0 rounded-3xl bg-gradient-to-r from-primary-500/5 via-transparent to-secondary-500/5 opacity-0 transition-opacity duration-300;
}
.hb-notebook:hover::after {
@apply opacity-100;
}
.hb-notebook--dense {
@apply p-4;
}
.hb-notebook-header {
@apply flex flex-wrap items-start justify-between gap-4 border-b border-zinc-200/70 pb-4 dark:border-zinc-800/70;
}
.hb-notebook-heading {
@apply space-y-1;
}
.hb-notebook-title {
@apply text-lg font-semibold text-zinc-900 dark:text-white tracking-tight;
}
.hb-notebook-subtitle {
@apply text-sm font-medium text-zinc-500 dark:text-zinc-400;
}
.hb-notebook-download {
@apply inline-flex items-center gap-2 rounded-full border border-primary-500/70 px-4 py-2 text-sm font-medium text-primary-600 no-underline transition-all duration-200;
@apply hover:-translate-y-0.5 hover:bg-primary-50/60 hover:text-primary-700 dark:text-primary-300 dark:border-primary-400/60 dark:hover:bg-primary-500/10;
}
.hb-notebook-download svg {
@apply h-4 w-4;
}
.hb-notebook-metadata {
@apply mt-4 grid gap-3 rounded-2xl border border-dashed border-zinc-200/70 px-4 py-3 text-sm;
@apply dark:border-zinc-800/70;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
.hb-notebook-metadata dt {
@apply text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400;
}
.hb-notebook-metadata dd {
@apply font-medium text-zinc-800 dark:text-zinc-100;
}
.hb-notebook-body {
@apply mt-6 flex flex-col gap-5;
}
.hb-notebook-cell {
@apply rounded-2xl border border-zinc-200/70 bg-white p-4 shadow-sm shadow-zinc-200/60 transition-all duration-200;
@apply dark:border-zinc-800/60 dark:bg-zinc-900/70 dark:shadow-none;
}
.hb-notebook-cell--markdown {
@apply bg-gradient-to-br from-white via-white to-primary-50/40 dark:from-zinc-900 dark:via-zinc-900 dark:to-primary-900/10;
}
.hb-notebook-cell-header {
@apply mb-3 flex flex-wrap items-center gap-3;
}
.hb-notebook-pill {
@apply inline-flex items-center rounded-full bg-zinc-100 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-zinc-600;
@apply dark:bg-zinc-800 dark:text-zinc-300;
}
.hb-notebook-tags {
@apply flex flex-wrap gap-1;
}
.hb-notebook-tags span {
@apply inline-flex items-center rounded-full bg-primary-100/80 px-2 py-0.5 text-[11px] font-medium text-primary-700;
@apply dark:bg-primary-400/20 dark:text-primary-200;
}
.hb-notebook-code {
@apply overflow-hidden rounded-2xl border border-zinc-900/10 bg-zinc-950/95 shadow-inner shadow-black/30;
}
.hb-notebook-code pre {
@apply m-0 overflow-auto rounded-2xl p-5 text-sm leading-relaxed;
}
.hb-notebook-markdown {
@apply prose-h1:text-2xl prose-h2:text-xl prose-p:leading-relaxed prose-a:text-primary-600 max-w-none;
}
.hb-notebook-raw {
@apply rounded-xl border border-dashed border-zinc-300/80 bg-zinc-50 p-4 font-mono text-sm text-zinc-700;
@apply dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 overflow-auto;
}
.hb-notebook-outputs {
@apply mt-4 space-y-3 rounded-2xl border border-zinc-100/80 bg-zinc-50/80 p-4;
@apply dark:border-zinc-800/60 dark:bg-zinc-900/50;
}
.hb-notebook-output {
@apply overflow-auto rounded-xl border border-transparent bg-white/90 p-3 text-sm leading-relaxed text-zinc-700;
@apply dark:bg-zinc-950/50 dark:text-zinc-100;
max-height: var(--hb-notebook-output-max-height, 26rem);
}
.hb-notebook-output--stream {
@apply font-mono bg-zinc-900 text-zinc-100;
}
.hb-notebook-output--error {
@apply border-red-200 bg-red-50/90 text-red-700 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-200;
}
.hb-notebook-output--image {
@apply bg-transparent p-0 border-none;
}
.hb-notebook-output--image img,
.hb-notebook-output--image svg {
@apply h-auto w-full rounded-xl border border-zinc-100/80 bg-white object-contain dark:border-zinc-800/60 dark:bg-zinc-900;
}
.hb-notebook-output--markdown {
@apply bg-transparent p-0 border-none;
}
.hb-notebook-output pre {
@apply m-0;
}
.hb-notebook-output code {
@apply font-mono text-sm;
}
.hb-notebook-empty {
@apply mt-4 rounded-2xl border border-dashed border-zinc-300/70 bg-zinc-50/80 px-4 py-3 text-center text-sm text-zinc-500;
@apply dark:border-zinc-700 dark:bg-zinc-900/40 dark:text-zinc-300;
}
@@ -89,4 +89,15 @@
background-color: var(--color-primary-400);
color: black;
}
/* Inline Code Styling */
.prose code:not(:where(pre *)) {
@apply rounded-md bg-neutral-100 px-1.5 py-0.5 text-[0.875em] font-normal text-primary-700;
@apply dark:bg-neutral-700/50 dark:text-primary-300;
}
.prose code:not(:where(pre *))::before,
.prose code:not(:where(pre *))::after {
content: none !important;
}
}
@@ -0,0 +1,81 @@
/*
Hugo Blox Layout Utilities
Provides CSS utilities that use the configurable layout tokens:
- --hb-radius: Border radius token
- --hb-spacing-base: Base spacing unit
- --hb-spacing-section: Section padding
- --hb-font-size-base: Base font size
- --hb-font-size-scale: Modular scale ratio
*/
/* Border radius utility - use instead of hardcoded rounded-* classes */
.hb-rounded {
border-radius: var(--hb-radius, 0.5rem);
}
.hb-rounded-sm {
border-radius: calc(var(--hb-radius, 0.5rem) * 0.5);
}
.hb-rounded-lg {
border-radius: calc(var(--hb-radius, 0.5rem) * 1.5);
}
.hb-rounded-xl {
border-radius: calc(var(--hb-radius, 0.5rem) * 2);
}
/* Section spacing */
.hb-section {
padding-top: var(--hb-spacing-section, 4rem);
padding-bottom: var(--hb-spacing-section, 4rem);
}
.hb-section-sm {
padding-top: calc(var(--hb-spacing-section, 4rem) * 0.5);
padding-bottom: calc(var(--hb-spacing-section, 4rem) * 0.5);
}
/* Component spacing */
.hb-gap {
gap: var(--hb-spacing-base, 1rem);
}
.hb-gap-sm {
gap: calc(var(--hb-spacing-base, 1rem) * 0.5);
}
.hb-gap-lg {
gap: calc(var(--hb-spacing-base, 1rem) * 1.5);
}
/* Padding utilities */
.hb-p {
padding: var(--hb-spacing-base, 1rem);
}
.hb-px {
padding-left: var(--hb-spacing-base, 1rem);
padding-right: var(--hb-spacing-base, 1rem);
}
.hb-py {
padding-top: var(--hb-spacing-base, 1rem);
padding-bottom: var(--hb-spacing-base, 1rem);
}
/* Margin utilities */
.hb-m {
margin: var(--hb-spacing-base, 1rem);
}
.hb-mx {
margin-left: var(--hb-spacing-base, 1rem);
margin-right: var(--hb-spacing-base, 1rem);
}
.hb-my {
margin-top: var(--hb-spacing-base, 1rem);
margin-bottom: var(--hb-spacing-base, 1rem);
}
@@ -26,6 +26,9 @@
/* Hugo Blox theme system (color utilities) */
@import "color-utilities.css";
/* Hugo Blox layout utilities (radius, spacing tokens) */
@import "layout-utilities.css";
/* Hugo Blox framework components */
@import "framework/components.css";
@@ -1,29 +0,0 @@
name: "americano"
type: "dark"
dark:
colors:
primary: "#d49255" # Warm Orange/Brown (approximated from oklch(71.996% 0.123 62.756))
secondary: "#395260" # Cool Slate Blue (approximated from oklch(34.465% 0.029 199.194))
neutral: "#2b262b" # Deep Purple/Black (approximated from oklch(16.51% 0.015 326.261))
ui:
background: "#2b262b" # Deep Espresso
foreground: "#e0cab6" # Crema (approximated from oklch(72.354% 0.092 79.129))
header:
background: "#2b262b"
foreground: "#e0cab6"
footer:
background: "#231f23" # Darker roast
foreground: "#9ca3af"
light:
# A light "Cortado" pairing for day mode if needed
colors:
primary: "#d49255"
secondary: "#395260"
ui:
background: "#f5f5f4"
foreground: "#292524"
header:
background: "#ffffff"
foreground: "#292524"
@@ -0,0 +1,45 @@
# HugoBlox Theme Pack
# https://docs.hugoblox.com
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Coffee"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "Warm latte light mode, deep americano dark mode"
type: "dual"
light:
# "Latte" - Warm, creamy, light
colors:
primary: "#3b2313" # Mocha/Coffee bean
secondary: "#d97706" # Caramel/Amber
neutral: "#78716c" # Stone
ui:
background: "#fffaf5" # Warm cream/milk foam
foreground: "#3b2313" # Espresso text
header:
background: "#f5efe9" # Visible header band (same as footer)
foreground: "#3b2313"
footer:
background: "#f5efe9" # Slightly darker latte foam
foreground: "#5c4033"
dark:
# "Americano" - Deep, dark, intense
colors:
primary: "#d49255" # Warm Orange/Brown crema
secondary: "#395260" # Cool Slate Blue
neutral: "#2b262b" # Deep Espresso (Dark Neutral)
ui:
background: "#2b262b" # Deep Espresso
foreground: "#e0cab6" # Crema
header:
background: "#2b262b"
foreground: "#e0cab6"
footer:
background: "#231f23" # Darker roast
foreground: "#9ca3af"
+39 -28
View File
@@ -1,30 +1,41 @@
name: "contrast"
type: "dual"
# HugoBlox Theme Pack
# https://docs.hugoblox.com
light:
colors:
primary: "indigo"
secondary: "blue"
ui:
background: "#ffffff"
foreground: "#111827"
header:
foreground: "#ffffff"
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Contrast"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "High contrast with inverted header"
type: "dual"
light:
colors:
primary: "indigo"
secondary: "blue"
ui:
background: "#ffffff"
foreground: "#111827"
header:
foreground: "#ffffff"
background: "#0f172a"
footer:
background: "#f3f4f6"
foreground: "#0f172a"
dark:
colors:
primary: "blue"
secondary: "teal"
ui:
background: "#0f172a"
footer:
background: "#f3f4f6"
foreground: "#0f172a"
dark:
colors:
primary: "blue"
secondary: "teal"
ui:
background: "#0f172a"
foreground: "#f8fafc"
header:
foreground: "#0f172a"
background: "#f8fafc"
footer:
background: "#111827"
foreground: "#e5e7eb"
foreground: "#f8fafc"
header:
foreground: "#0f172a"
background: "#f8fafc"
footer:
background: "#111827"
foreground: "#e5e7eb"
+42 -27
View File
@@ -1,29 +1,44 @@
name: "cupcake"
type: "light"
# HugoBlox Theme Pack
# https://docs.hugoblox.com
light:
colors:
primary: "#e779c1" # Frosting Pink (approximated from oklch(65% 0.241 354.308))
secondary: "#d946ef" # Deep Purple/Fuchsia (approximated from oklch(62% 0.265 303.9))
neutral: "#5c4033" # Chocolate/Dark Brown (approximated from oklch(40% 0.153 2.432))
ui:
background: "#faf7f5" # Vanilla/Sugar (approximated from oklch(97% 0.014 343.198))
foreground: "#7d1f39" # Berry Jam (approximated from oklch(52% 0.223 3.958))
header:
background: "#ffffff"
foreground: "#7d1f39"
footer:
background: "#f3e8e8" # Light Pink Foam
foreground: "#7d1f39"
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Cupcake"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "Sweet pastry pinks and berry accents"
dark:
# A darker "Red Velvet" variant
colors:
primary: "#e779c1"
secondary: "#d946ef"
ui:
background: "#2a1b20" # Dark Chocolate
foreground: "#f8e7e7" # Pale Pink Cream
header:
background: "#1f1216"
foreground: "#f8e7e7"
type: "dual"
light:
colors:
primary: "#e779c1" # Frosting Pink
secondary: "#d946ef" # Deep Purple/Fuchsia
neutral: "#5c4033" # Chocolate/Dark Brown
ui:
background: "#faf7f5" # Vanilla/Sugar
foreground: "#7d1f39" # Berry Jam
header:
background: "#fff0f5" # LavenderBlush - slight pink tint
foreground: "#7d1f39"
footer:
background: "#f3e8e8" # Light Pink Foam
foreground: "#7d1f39"
dark:
# "Dark Berry" - Rich, vibrant, fruity
colors:
primary: "#f472b6" # Bright Icing Pink (Pink-400)
secondary: "#c084fc" # Purple Sprinkle (Purple-400)
neutral: "#831843" # Deep Berry (Pink-900)
ui:
background: "#4c0519" # Deep Red Velvet / Rose-950
foreground: "#fce7f3" # Sweet Cream / Pink-100
header:
background: "#831843" # Vibrant Berry Header / Pink-900
foreground: "#fce7f3"
footer:
background: "#881337" # Rose-900
foreground: "#fbcfe8" # Pink-200
+35 -25
View File
@@ -1,30 +1,40 @@
name: "default"
type: "dual"
# HugoBlox Theme Pack
# https://docs.hugoblox.com
light:
colors:
primary: "indigo"
secondary: "blue"
ui:
background: "#ffffff"
foreground: "#111827"
header:
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Default"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
type: "dual"
light:
colors:
primary: "indigo"
secondary: "blue"
ui:
background: "#ffffff"
foreground: "#0f172a"
footer:
background: "#f3f4f6"
foreground: "#0f172a"
foreground: "#111827"
header:
background: "#f1f5f9" # Slate-100 for definition against white content
foreground: "#0f172a"
footer:
background: "#f3f4f6"
foreground: "#0f172a"
dark:
colors:
primary: "blue"
secondary: "teal"
ui:
background: "#0f172a"
foreground: "#f8fafc"
header:
dark:
colors:
primary: "blue"
secondary: "teal"
ui:
background: "#0f172a"
foreground: "#f8fafc"
footer:
background: "#111827"
foreground: "#e5e7eb"
header:
background: "#0f172a"
foreground: "#f8fafc"
footer:
background: "#111827"
foreground: "#e5e7eb"
+43 -27
View File
@@ -1,29 +1,45 @@
name: "dracula"
type: "dark"
# HugoBlox Theme Pack
# https://docs.hugoblox.com
dark:
colors:
primary: "#ff79c6" # Dracula Pink
secondary: "#bd93f9" # Dracula Purple
neutral: "#6272a4" # Dracula Comment (Blue-Gray)
ui:
background: "#282a36" # Dracula Background
foreground: "#f8f8f2" # Dracula Foreground
header:
background: "#282a36"
foreground: "#f8f8f2"
footer:
background: "#21222c" # Slightly darker selection color
foreground: "#f8f8f2"
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Dracula"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "The iconic dark theme with a light Buffy variant"
# Optional light variant (Dracula usually doesn't have one, but we can provide a high contrast or soft variant)
light:
colors:
primary: "#ff79c6"
secondary: "#bd93f9"
ui:
background: "#f8f8f2"
foreground: "#282a36"
header:
background: "#ffffff"
foreground: "#282a36"
type: "dual"
dark:
# Official Dracula Dark
colors:
primary: "#ff79c6" # Pink
secondary: "#bd93f9" # Purple
neutral: "#6272a4" # Comment
ui:
background: "#282a36" # Background
foreground: "#f8f8f2" # Foreground
header:
background: "#282a36"
foreground: "#f8f8f2"
footer:
background: "#21222c" # Selection
foreground: "#f8f8f2"
light:
# Dracula Light (Buffy inspired) - Inverted aesthetic
colors:
primary: "#d03282" # Darker Pink for contrast
secondary: "#9259d6" # Darker Purple for contrast
neutral: "#44475a" # Current Line (Dark Gray)
ui:
background: "#f8f8f2" # Light Gray
foreground: "#282a36" # Dark Gray
header:
background: "#e4e4db" # Slightly darker band
foreground: "#282a36"
footer:
background: "#e4e4db"
foreground: "#282a36"
@@ -1,32 +0,0 @@
name: "latte"
type: "light"
light:
colors:
primary: "#3b2313" # Mocha/Coffee bean
secondary: "#d97706" # Caramel/Amber
neutral: "#78716c" # Stone
ui:
background: "#fffaf5" # Warm cream/milk foam
foreground: "#3b2313" # Espresso text
header:
background: "rgba(255, 250, 245, 0.85)" # Frothy semi-transparent
foreground: "#3b2313"
footer:
background: "#f5efe9" # Slightly darker latte foam
foreground: "#5c4033"
dark:
# A rich "Espresso Martini" dark mode partner
colors:
primary: "#d97706" # Caramel highlights
secondary: "#f59e0b" # Gold foam
ui:
background: "#1c1917" # Dark roasted bean
foreground: "#e7e5e4" # Steam
header:
background: "#1c1917"
foreground: "#e7e5e4"
footer:
background: "#292524"
foreground: "#a8a29e"
+37 -26
View File
@@ -1,29 +1,40 @@
name: "marine"
type: "dark"
# HugoBlox Theme Pack
# https://docs.hugoblox.com
dark:
colors:
primary: "#2dd4bf" # Teal/Cyan (approximated from oklch(85.661% 0.144 198.645))
secondary: "#a855f7" # Purple (approximated from oklch(60.682% 0.108 309.782))
neutral: "#1e3a8a" # Deep Blue (approximated from oklch(27% 0.146 265.522))
ui:
background: "#1e3a8a" # Deep Ocean Blue (from oklch(37% 0.146 265.522))
foreground: "#e0f2fe" # Light Sky (approximated from oklch(90% 0.058 230.902))
header:
background: "#1e3a8a"
foreground: "#e0f2fe"
footer:
background: "#172554" # Darker depth
foreground: "#93c5fd"
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Marine"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "Deep ocean blues with teal accents"
light:
# A "Beach Day" pairing
colors:
primary: "#2dd4bf"
secondary: "#a855f7"
ui:
background: "#f0f9ff"
foreground: "#0c4a6e"
header:
background: "#ffffff"
type: "dual"
dark:
colors:
primary: "#2dd4bf" # Teal/Cyan
secondary: "#a855f7" # Purple
neutral: "#1e3a8a" # Deep Blue
ui:
background: "#1e3a8a" # Deep Ocean Blue
foreground: "#e0f2fe" # Light Sky
header:
background: "#1e3a8a"
foreground: "#e0f2fe"
footer:
background: "#172554" # Darker depth
foreground: "#93c5fd"
light:
# A "Beach Day" pairing
colors:
primary: "#2dd4bf"
secondary: "#a855f7"
ui:
background: "#f0f9ff"
foreground: "#0c4a6e"
header:
background: "#e0f2fe" # Sky-100 for visible header bar
foreground: "#0c4a6e"
@@ -0,0 +1,45 @@
# HugoBlox Theme Pack
# https://docs.hugoblox.com
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Matcha"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "Fresh, organic greens inspired by Japanese tea"
type: "dual"
light:
# "Matcha Latte" - Creamy, fresh, organic
colors:
primary: "#65a30d" # Fresh Tea Leaf (Lime-600)
secondary: "#15803d" # Deep Herbal Green (Green-700)
neutral: "#3f6212" # Olive text (Lime-800)
ui:
background: "#f7fee7" # Very pale lime cream (Lime-50)
foreground: "#1a2e05" # Dark moss text
header:
background: "#ecfccb" # Light Matcha Foam (Lime-100)
foreground: "#365314" # Deep olive
footer:
background: "#d9f99d" # Stronger tea green (Lime-200)
foreground: "#14532d"
dark:
# "Ceremonial Grade" - Deep, rich, meditative
colors:
primary: "#a3e635" # Vibrant Matcha Powder (Lime-400)
secondary: "#4ade80" # Soft green light (Green-400)
neutral: "#1a2f23" # Deep Forest Background
ui:
background: "#14281d" # Deep Nori Green
foreground: "#ecfccb" # Pale tea green text
header:
background: "#0f1f16" # Darker leaf
foreground: "#d9f99d"
footer:
background: "#052e16" # Deepest green (Green-950)
foreground: "#86efac"
@@ -0,0 +1,45 @@
# HugoBlox Theme Pack
# https://docs.hugoblox.com
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Minimal"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "Clean, GitHub-inspired design"
type: "dual"
light:
# Inspired by GitHub Light
colors:
primary: "#0969da" # GitHub Blue
secondary: "#1f883d" # GitHub Green
neutral: "#24292f" # GitHub Dark Gray (Text)
ui:
background: "#ffffff"
foreground: "#24292f"
header:
background: "#f6f8fa" # GitHub Header Gray
foreground: "#24292f"
footer:
background: "#ffffff" # Clean
foreground: "#57606a" # Muted text
dark:
# Inspired by GitHub Dark
colors:
primary: "#58a6ff" # GitHub Blue (Dark Mode)
secondary: "#238636" # GitHub Green (Dark Mode)
neutral: "#c9d1d9" # GitHub Light Gray (Text)
ui:
background: "#0d1117" # GitHub Dimmed BG
foreground: "#c9d1d9"
header:
background: "#161b22" # GitHub Dark Header
foreground: "#f0f6fc"
footer:
background: "#0d1117"
foreground: "#8b949e" # Muted text
+43 -27
View File
@@ -1,29 +1,45 @@
name: "retro"
type: "light"
# HugoBlox Theme Pack
# https://docs.hugoblox.com
light:
colors:
primary: "#e38c89" # Desaturated Red/Pink (approximated from oklch(80% 0.114 19.571))
secondary: "#b4e9d6" # Mint Green (approximated from oklch(92% 0.084 155.995))
neutral: "#79726b" # Warm Stone (approximated from oklch(44% 0.011 73.639))
ui:
background: "#ebe6d9" # Vintage Paper / Beige (approximated from oklch(91.637% 0.034 90.515))
foreground: "#7d5e3c" # Sepia/Brown (approximated from oklch(41% 0.112 45.904))
header:
background: "#e3dbcd" # Slightly darker paper
foreground: "#7d5e3c"
footer:
background: "#dcd3c3"
foreground: "#5c4033"
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Retro"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "Vintage paper meets midnight arcade"
dark:
# A dark "Polaroid" variant
colors:
primary: "#e38c89"
secondary: "#b4e9d6"
ui:
background: "#292524" # Dark Stone
foreground: "#e7e5e4" # Warm Gray
header:
background: "#1c1917"
foreground: "#e7e5e4"
type: "dual"
light:
# "Vintage Paper" - Sepia, warm, nostalgic
colors:
primary: "#e38c89" # Desaturated Red/Pink
secondary: "#b4e9d6" # Mint Green
neutral: "#79726b" # Warm Stone
ui:
background: "#ebe6d9" # Vintage Paper / Beige
foreground: "#7d5e3c" # Sepia/Brown
header:
background: "#e3dbcd" # Slightly darker paper
foreground: "#7d5e3c"
footer:
background: "#dcd3c3"
foreground: "#5c4033"
dark:
# "Midnight Arcade" - Deep petrol, mustard, and neon accents
colors:
primary: "#fbbf24" # Mustard Gold (Amber-400)
secondary: "#f472b6" # Retro Pink (Pink-400)
neutral: "#134e4a" # Dark Teal (Teal-900)
ui:
background: "#042f2e" # Deep Petrol / Teal-950
foreground: "#ccfbf1" # Pale Mint / Teal-50
header:
background: "#115e59" # Lighter Petrol / Teal-800
foreground: "#ccfbf1"
footer:
background: "#0f393b" # Muted Teal
foreground: "#99f6e4" # Teal-200
@@ -0,0 +1,45 @@
# HugoBlox Theme Pack
# https://docs.hugoblox.com
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Solar"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "Solarized-inspired, easy on the eyes"
type: "dual"
light:
# Inspired by Solarized Light - Low contrast, warm, easy on the eyes
colors:
primary: "#b58900" # Yellow (Solarized)
secondary: "#2aa198" # Cyan (Solarized)
neutral: "#657b83" # Base00 (Primary Content)
ui:
background: "#fdf6e3" # Base3 (Creamy Background)
foreground: "#586e75" # Base01 (Optional darker text for contrast)
header:
background: "#eee8d5" # Base2 (Slightly darker highlights)
foreground: "#586e75"
footer:
background: "#eee8d5"
foreground: "#657b83"
dark:
# Inspired by Solarized Dark - Deep, cool, high precision
colors:
primary: "#268bd2" # Blue (Solarized)
secondary: "#859900" # Green (Solarized)
neutral: "#839496" # Base0 (Primary Content)
ui:
background: "#002b36" # Base03 (Deep Teal Background)
foreground: "#93a1a1" # Base1 (Light Content)
header:
background: "#073642" # Base02 (Lighter Teal Highlights)
foreground: "#93a1a1"
footer:
background: "#073642"
foreground: "#839496"
@@ -1,29 +1,44 @@
name: "synthwave"
type: "dark"
# HugoBlox Theme Pack
# https://docs.hugoblox.com
dark:
colors:
primary: "#e779c1" # Neon Pink (approx oklch(71% 0.202 349.761))
secondary: "#58c7f3" # Cyan (approx oklch(82% 0.111 230.318))
neutral: "#2a2e37" # Dark Blue-Gray (approx oklch(45% 0.24 277.023) adjusted for readability)
ui:
background: "#1a103d" # Deep Purple (approx oklch(15% 0.09 281.288))
foreground: "#f9f7fd" # Light Lavender (approx oklch(78% 0.115 274.713))
header:
background: "rgba(26, 16, 61, 0.9)" # Deep Purple
foreground: "#f9f7fd"
footer:
background: "#110a28" # Darker Deep Purple
foreground: "#b8b2cc"
hugoblox:
meta:
format: "hugoblox-theme@1"
name: "Synthwave"
vendor: "hugoblox"
version: "1.0.0"
license: "MIT"
description: "Neon-drenched 80s retrowave aesthetic"
light:
# Synthwave usually doesn't have a light mode, providing a high contrast fallback
colors:
primary: "#e779c1"
secondary: "#58c7f3"
ui:
background: "#f9f7fd"
foreground: "#1a103d"
header:
background: "#ffffff"
foreground: "#1a103d"
type: "dual"
dark:
colors:
primary: "#e779c1" # Neon Pink
secondary: "#58c7f3" # Cyan
neutral: "#2a2e37" # Dark Blue-Gray
ui:
background: "#1a103d" # Deep Purple
foreground: "#f9f7fd" # Light Lavender
header:
background: "rgba(26, 16, 61, 0.9)" # Deep Purple
foreground: "#f9f7fd"
footer:
background: "#110a28" # Darker Deep Purple
foreground: "#b8b2cc"
light:
# "Vaporwave" - A bright, pastel-neon day dream
colors:
primary: "#d946ef" # Electric Fuchsia
secondary: "#06b6d4" # Neon Cyan
neutral: "#701a75" # Deep Magenta (for text utilities)
ui:
background: "#fff0f5" # Lavender Blush
foreground: "#2e1065" # Deep Violet Ink
header:
background: "#fae8ff" # Light Fuchsia Mist
foreground: "#86198f" # Vibrant Purple
footer:
background: "#f0abfc" # Sunset Pink
foreground: "#2e1065" # Dark Violet
@@ -72,8 +72,8 @@
>
</li>
{{ end }}
{{ $show_theme_chooser := site.Params.header.navbar.show_theme_chooser | default true }}
{{ if $show_theme_chooser }}
{{ $show_mode_toggle := site.Params.header.navbar.show_mode_toggle | default site.Params.header.navbar.show_theme_chooser | default true }}
{{ if $show_mode_toggle }}
<li class="lg:px-2 text-[var(--color-header-fg)] hover:text-primary-500
[&.active]:font-bold [&.active]:text-primary-500">
<button class="theme-toggle mt-1" accesskey="t" title="">
@@ -98,6 +98,31 @@
</button>
</li>
{{ end }}
{{/* Theme Picker */}}
{{ if site.Params.header.navbar.show_theme_picker }}
<li class="lg:px-2">
<select onchange="document.documentElement.setAttribute('data-theme-pack', this.value); localStorage.setItem('hb-theme-pack', this.value);" class="bg-transparent text-[var(--color-header-fg)] border-none focus:ring-0 cursor-pointer text-sm font-medium">
<option value="">Theme</option>
{{ range $name, $data := site.Data.themes }}
<option value="{{ $name }}">{{ $name | humanize }}</option>
{{ end }}
</select>
<script>
(function() {
const savedTheme = localStorage.getItem('hb-theme-pack');
if (savedTheme) {
document.documentElement.setAttribute('data-theme-pack', savedTheme);
document.addEventListener('DOMContentLoaded', () => {
const select = document.querySelector('select[onchange*="data-theme-pack"]');
if (select) select.value = savedTheme;
});
}
})();
</script>
</li>
{{ end }}
{{ $show_translations := site.Params.header.navbar.show_translations | default true }}
{{ if .IsTranslated | and $show_translations }}
<li class="lg:px-2 text-[var(--color-header-fg)] hover:text-primary-500
@@ -1,31 +1,27 @@
{{ $branding := partialCached "functions/get_branding" . "branding" }}
{{ $logo := partialCached "functions/get_logo" (dict "constraint" "max_height" "size" 36) "navbar_logo" }}
<header id="site-header" class="header">
<nav class="navbar px-3 flex justify-start">
<div class="order-0 h-full">
<!-- Brand -->
<a class="navbar-brand" href="{{ site.Home.RelPermalink }}" title="{{ site.Title }}">
{{- $logo_filename := site.Params.header.navbar.logo.filename -}}
{{ with $logo_filename }}
{{- $logo:= resources.Get (path.Join "media" $logo_filename) -}}
{{ with $logo }}
{{ $logoExt := path.Ext . }}
{{ if eq $logoExt `.svg` }}
{{- $logo.Content | safeHTML -}}
{{else}}
{{ $logo_img := ($logo.Process "Resize x36 webp") }}
<img
fetchpriority="high"
decoding="async"
class=""
width="{{ $logo_img.Width }}"
height="{{ $logo_img.Height }}"
src="{{ $logo_img.RelPermalink }}"
alt="{{ site.Title }}" />
{{end}}
<a class="navbar-brand" href="{{ site.Home.RelPermalink }}" title="{{ $branding.site_title }}">
{{/* Auto-detect logo from assets/media/logo.{svg,png} */}}
{{ with $logo.resource }}
{{ if $logo.is_svg }}
{{- .Content | safeHTML -}}
{{ else }}
{{ warnf "Logo `%s` not found in `assets/media/` folder" $logo_filename }}
{{end}}
<img
fetchpriority="high"
decoding="async"
class=""
width="{{ .Width }}"
height="{{ .Height }}"
src="{{ .RelPermalink }}"
alt="{{ $branding.name }}" />
{{ end }}
{{ end }}
{{ with site.Params.header.navbar.logo.text }}{{.}}{{end}}
{{/* Display brand name */}}
{{ $branding.name }}
</a>
</div>
<!-- Mobile menu -->
@@ -148,8 +144,8 @@
</button>
{{ end }}
{{ $show_theme_chooser := site.Params.header.navbar.show_theme_chooser | default true }}
{{ if $show_theme_chooser }}
{{ $show_mode_toggle := site.Params.header.navbar.show_mode_toggle | default site.Params.header.navbar.show_theme_chooser | default true }}
{{ if $show_mode_toggle }}
<div class="px-3 text-[var(--color-header-fg)] hover:text-primary-500
[&.active]:font-bold [&.active]:text-primary-500">
<button class="theme-toggle mt-1" accesskey="t" title="appearance">
@@ -175,6 +171,31 @@
</div>
{{ end }}
{{/* Theme Picker */}}
{{ if site.Params.header.navbar.show_theme_picker }}
<div class="px-3">
<select onchange="document.documentElement.setAttribute('data-theme-pack', this.value); localStorage.setItem('hb-theme-pack', this.value);" class="bg-transparent text-[var(--color-header-fg)] border-none focus:ring-0 cursor-pointer text-sm font-medium">
<option value="">Theme</option>
{{ range $name, $data := site.Data.themes }}
<option value="{{ $name }}">{{ $name | humanize }}</option>
{{ end }}
</select>
<script>
(function() {
const savedTheme = localStorage.getItem('hb-theme-pack');
if (savedTheme) {
document.documentElement.setAttribute('data-theme-pack', savedTheme);
// Defer selecting the option until DOM is ready
document.addEventListener('DOMContentLoaded', () => {
const select = document.querySelector('select[onchange*="data-theme-pack"]');
if (select) select.value = savedTheme;
});
}
})();
</script>
</div>
{{ end }}
{{ $show_translations := site.Params.header.navbar.show_translations | default true }}
{{ if .IsTranslated | and $show_translations }}
<div class="pl-1 mr-5 text-[var(--color-header-fg)] hover:text-primary-500
@@ -0,0 +1,31 @@
{{/*
HugoBlox Theme Engine: Demo Theme Styles
Generates CSS variables for ALL available theme packs.
Used for the "Theme Pack Chooser" demo feature.
*/}}
{{ $typography_config := .typography }}
{{ $layout_config := .layout }}
<style>
{{ range $pack_name, $pack_data := site.Data.themes }}
{{ if $pack_data.hugoblox }}
{{ $theme := $pack_data.hugoblox }}
{{/* Get light/dark mode data */}}
{{ $light_data := $theme.light | default $theme }}
{{ $dark_data := $theme.dark | default $theme }}
{{/* 1. Light Mode Variables */}}
[data-theme-pack="{{ $pack_name }}"] {
{{ partial "functions/theme_variables" (dict "data" $light_data "is_dark" false "typography" $typography_config "layout" $layout_config) | safeCSS }}
}
{{/* 2. Dark Mode Variables */}}
[data-theme-pack="{{ $pack_name }}"].dark,
.dark [data-theme-pack="{{ $pack_name }}"] {
{{ partial "functions/theme_variables" (dict "data" $dark_data "is_dark" true "typography" $typography_config "layout" $layout_config) | safeCSS }}
}
{{ end }}
{{ end }}
</style>
@@ -47,6 +47,7 @@
{{/* Base (500) */}}
--hb-{{$name}}-500-rgb: {{ $r }} {{ $g }} {{ $b }};
--color-{{$name}}-500: rgb(var(--hb-{{$name}}-500-rgb));
{{/* Tints (50-400): Mix with White */}}
{{ $tints := slice
@@ -62,6 +63,7 @@
{{ $gp := div (add (mul $g (sub 100 $p)) (mul 255 $p)) 100 }}
{{ $bp := div (add (mul $b (sub 100 $p)) (mul 255 $p)) 100 }}
--hb-{{$name}}-{{ .shade }}-rgb: {{ $rp }} {{ $gp }} {{ $bp }};
--color-{{$name}}-{{ .shade }}: rgb(var(--hb-{{$name}}-{{ .shade }}-rgb));
{{ end }}
{{/* Shades (600-950): Mix with Black */}}
@@ -78,6 +80,7 @@
{{ $gp := div (mul $g (sub 100 $p)) 100 }}
{{ $bp := div (mul $b (sub 100 $p)) 100 }}
--hb-{{$name}}-{{ .shade }}-rgb: {{ $rp }} {{ $gp }} {{ $bp }};
--color-{{$name}}-{{ .shade }}: rgb(var(--hb-{{$name}}-{{ .shade }}-rgb));
{{ end }}
{{ else }}
@@ -3,7 +3,8 @@
{{ $page := . }}
{{/* Get publisher as fall back. */}}
{{ $publisher := site.Params.marketing.seo.org_name | default site.Title }}
{{ $branding := partialCached "functions/get_branding" $page "branding" }}
{{ $publisher := $branding.organization }}
{{ $author := "" }}
{{ $author_username := "" }}
@@ -0,0 +1,77 @@
{{/*
get_branding.html - Centralized branding configuration helper
Returns a dict with:
- name: Display name (navbar, footer, JSON-LD publisher)
- site_title: Browser tab title / SEO title
- organization: Legal/copyright entity name
Fallback chain:
- site_title → name → site.Title
- organization → name → site.Title
Supports backward compatibility with deprecation warnings for:
- header.navbar.logo.text → hugoblox.branding.name
- marketing.seo.org_name → hugoblox.branding.organization
*/}}
{{/* New structure: hugoblox.branding */}}
{{ $hb := site.Params.hugoblox | default dict }}
{{ $branding := $hb.branding | default dict }}
{{/* ═══════════════════════════════════════════════════════════════════════════
NAME (primary display name)
Priority: hugoblox.branding.name → header.navbar.logo.text (deprecated) → site.Title
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $name := "" }}
{{/* Check new location first */}}
{{ with $branding.name }}
{{ $name = . }}
{{ else }}
{{/* Check deprecated location: header.navbar.logo.text */}}
{{ with site.Params.header.navbar.logo.text }}
{{ $name = . }}
{{ warnf "DEPRECATED: 'header.navbar.logo.text' in config/_default/params.yaml is deprecated. Please move to:\n\nhugoblox:\n branding:\n name: \"%s\"\n" . }}
{{ else }}
{{/* Ultimate fallback: Hugo's site.Title */}}
{{ $name = site.Title }}
{{ end }}
{{ end }}
{{/* ═══════════════════════════════════════════════════════════════════════════
SITE_TITLE (browser tab / SEO title)
Priority: hugoblox.branding.site_title → name
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $site_title := $branding.site_title | default $name }}
{{/* ═══════════════════════════════════════════════════════════════════════════
ORGANIZATION (legal/copyright entity)
Priority: hugoblox.branding.organization → marketing.seo.org_name (deprecated) → name
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $organization := "" }}
{{/* Check new location first */}}
{{ with $branding.organization }}
{{ $organization = . }}
{{ else }}
{{/* Check deprecated location: marketing.seo.org_name */}}
{{ with site.Params.marketing.seo.org_name }}
{{ if ne . "" }}
{{ $organization = . }}
{{ warnf "DEPRECATED: 'marketing.seo.org_name' in config/_default/params.yaml is deprecated. Please move to:\n\nhugoblox:\n branding:\n organization: \"%s\"\n" . }}
{{ else }}
{{ $organization = $name }}
{{ end }}
{{ else }}
{{/* Fallback to name */}}
{{ $organization = $name }}
{{ end }}
{{ end }}
{{/* Return branding dict */}}
{{ return dict
"name" $name
"site_title" $site_title
"organization" $organization
}}
@@ -1,21 +1,41 @@
{{/* Function to get logo image. */}}
{{/* Inputs: constraint: max_height/fit (str); size (int) */}}
{{/* Output: resource (obj) */}}
{{/*
HugoBlox Image Engine: Get website logo
SVG-first detection for crisp rendering at any size.
Auto-detects from assets/media/:
1. logo.svg (preferred - vector, infinite scalability)
2. logo.png (fallback - raster, processed by Hugo)
Inputs:
- constraint: "max_height" or "fit" (default: "fit")
- size: int - Target size in pixels
Output: dict with:
- resource: The logo resource (processed if PNG)
- type: MIME type ("image/svg+xml" or "image/png")
- is_svg: boolean
*/}}
{{/* Workaround fact Hugo does not support GetMatch in assets dir */}}
{{/* Hugo doesn't support image ops on SVG: https://discourse.gohugo.io/t/ho-do-i-convert-a-generic-resource-to-image-resource/22570/4 */}}
{{ $logo := resources.Get "media/logo.png" | default (resources.Get "media/logo.svg") }}
{{ $logo_proc := $logo }}
{{ $constraint := .constraint | default "fit" }}
{{ $size := .size | default 36 }}
{{ $result := dict "resource" nil "type" "" "is_svg" false }}
{{/* If the type of image can be resized by Hugo, resize it given a `.size` argument to the function. */}}
{{ if resources.Get "media/logo.png" }}
{{ if eq .constraint "max_height" }}
{{/* Resize logo to fit specified max height. */}}
{{ $logo_proc = ($logo.Resize (printf "x%s" (string .size))) }}
{{ else }}
{{/* Constrain logo to fit within specified dimensions. */}}
{{ $logo_proc = ($logo.Fit (printf "%sx%s" (string .size) (string .size))) }}
{{/* 1. Check for SVG first (preferred) */}}
{{ $logo_svg := resources.Get "media/logo.svg" }}
{{ if $logo_svg }}
{{ $result = dict "resource" $logo_svg "type" "image/svg+xml" "is_svg" true }}
{{ else }}
{{/* 2. Fallback to PNG */}}
{{ $logo_png := resources.Get "media/logo.png" }}
{{ if $logo_png }}
{{ $logo_processed := $logo_png }}
{{ if eq $constraint "max_height" }}
{{ $logo_processed = $logo_png.Resize (printf "x%d" $size) }}
{{ else }}
{{ $logo_processed = $logo_png.Fit (printf "%dx%d" $size $size) }}
{{ end }}
{{ $result = dict "resource" $logo_processed "type" "image/png" "is_svg" false }}
{{ end }}
{{ end }}
{{ return $logo_proc }}
{{ return $result }}
@@ -1,7 +1,42 @@
{{/* Function to get website icon image. */}}
{{/* Input: size (int) */}}
{{/* Output: resource (obj) */}}
{{/*
HugoBlox Image Engine: Get website favicon/icon
SVG-first detection for modern browser support.
Auto-detects from assets/media/:
1. icon.svg (preferred - vector, supports dark mode)
2. icon.png (fallback - raster, processed by Hugo)
Input: size (int) - Only used for PNG processing
Output: dict with:
- resource: The icon resource
- type: MIME type ("image/svg+xml" or "image/png")
- is_svg: boolean
*/}}
{{ $icon := resources.GetMatch "media/icon.png" }}
{{ $icon_resized := $icon.Fill (printf "%sx%s Center" (string .) (string .)) }}
{{ return $icon_resized }}
{{ $size := . | default 32 }}
{{ $result := dict "resource" nil "type" "" "is_svg" false }}
{{/* 1. Check for SVG first (preferred) */}}
{{ $icon_svg := resources.Get "media/icon.svg" }}
{{ if $icon_svg }}
{{ $result = dict "resource" $icon_svg "type" "image/svg+xml" "is_svg" true }}
{{ else }}
{{/* 2. Fallback to PNG */}}
{{ $icon_png := resources.Get "media/icon.png" }}
{{ if $icon_png }}
{{ $icon_resized := $icon_png.Fill (printf "%sx%s Center" (string $size) (string $size)) }}
{{ $result = dict "resource" $icon_resized "type" "image/png" "is_svg" false }}
{{ else }}
{{/* 3. Ultimate fallback: Try to generate from logo */}}
{{ $logo_png := resources.Get "media/logo.png" }}
{{ if $logo_png }}
{{ $icon_from_logo := $logo_png.Fill (printf "%sx%s Center" (string $size) (string $size)) }}
{{ $result = dict "resource" $icon_from_logo "type" "image/png" "is_svg" false }}
{{ else }}
{{/* 4. No icon found */}}
{{ warnf "No favicon found. Please add assets/media/icon.svg (preferred) or assets/media/icon.png" }}
{{ end }}
{{ end }}
{{ end }}
{{ return $result }}
@@ -0,0 +1,3 @@
{{/* Get page summary with fallback priority: Params.summary > Params.description > Params.abstract > Summary */}}
{{ $summary := .Params.summary | default .Params.description | default .Params.abstract | default .Summary }}
{{ return $summary }}
@@ -0,0 +1,180 @@
{{/*
HugoBlox Theme Engine: Get theme configuration
Returns a dict with:
- mode: 'light' | 'dark' | 'system'
- light: Light theme pack name (default: "default")
- dark: Dark theme pack name (default: "default")
- colors: Global color overrides { primary, secondary, neutral }
- colors_light: Light-mode specific color overrides
- colors_dark: Dark-mode specific color overrides
- ui: Semantic UI overrides { header_bg, footer_bg, etc. }
- typography: { font, size }
- layout: { radius, spacing }
Supports backward compatibility with deprecation warnings for:
- appearance.mode → hugoblox.theme.mode
- appearance.color → hugoblox.theme.colors.primary
- appearance.theme.light/dark → hugoblox.theme.pack.light/dark
- appearance.font → hugoblox.typography.font
*/}}
{{/* New structure: hugoblox.theme */}}
{{ $hb := site.Params.hugoblox | default dict }}
{{ $hb_theme := $hb.theme | default dict }}
{{/* Legacy structure: appearance */}}
{{ $appearance := site.Params.appearance | default dict }}
{{ $legacy_theme := $appearance.theme | default dict }}
{{/* ═══════════════════════════════════════════════════════════════════════════
MODE (light/dark/system)
Priority: hugoblox.theme.mode → appearance.mode → "system"
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $mode := "system" }}
{{ with $hb_theme.mode }}
{{ $mode = . }}
{{ else }}
{{ with $appearance.mode }}
{{ $mode = . }}
{{/* Only warn if using old structure AND new structure exists */}}
{{ if $hb_theme }}
{{ warnf "DEPRECATED: 'appearance.mode' in config/_default/params.yaml is deprecated. Please move to:\n\nhugoblox:\n theme:\n mode: \"%s\"\n" . }}
{{ end }}
{{ end }}
{{ end }}
{{/* ═══════════════════════════════════════════════════════════════════════════
THEME PACK SELECTION (light/dark)
Priority: hugoblox.theme.pack → hugoblox.theme.light/dark → appearance.theme.light/dark → "default"
Pack can be:
- String: "default" (applies to both)
- Object: { light: "latte", dark: "americano" }
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $light_pack := "default" }}
{{ $dark_pack := "default" }}
{{/* Check hugoblox.theme.pack first */}}
{{ with $hb_theme.pack }}
{{ if reflect.IsMap . }}
{{ $light_pack = .light | default "default" }}
{{ $dark_pack = .dark | default "default" }}
{{ else }}
{{/* Single string applies to both */}}
{{ $light_pack = . }}
{{ $dark_pack = . }}
{{ end }}
{{ else }}
{{/* Check hugoblox.theme.light/dark (alternative flat structure) */}}
{{ with $hb_theme.light }}
{{ $light_pack = . }}
{{ else }}
{{/* Check legacy appearance.theme.light */}}
{{ with $legacy_theme.light }}
{{ $light_pack = . }}
{{ if $hb_theme }}
{{ warnf "DEPRECATED: 'appearance.theme.light' in config/_default/params.yaml is deprecated. Please move to:\n\nhugoblox:\n theme:\n pack:\n light: \"%s\"\n" . }}
{{ end }}
{{ end }}
{{ end }}
{{ with $hb_theme.dark }}
{{ $dark_pack = . }}
{{ else }}
{{/* Check legacy appearance.theme.dark */}}
{{ with $legacy_theme.dark }}
{{ $dark_pack = . }}
{{ if $hb_theme }}
{{ warnf "DEPRECATED: 'appearance.theme.dark' in config/_default/params.yaml is deprecated. Please move to:\n\nhugoblox:\n theme:\n pack:\n dark: \"%s\"\n" . }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{/* ═══════════════════════════════════════════════════════════════════════════
GLOBAL COLOR OVERRIDES
Priority: hugoblox.theme.colors → appearance.theme.colors → appearance.color (primary only)
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $colors := dict }}
{{ with $hb_theme.colors }}
{{ $colors = . }}
{{ else }}
{{ with $legacy_theme.colors }}
{{ $colors = . }}
{{ else }}
{{/* Check legacy appearance.color (primary only) */}}
{{ with $appearance.color }}
{{ $colors = dict "primary" . }}
{{ warnf "DEPRECATED: 'appearance.color' in config/_default/params.yaml is deprecated. Please move to:\n\nhugoblox:\n theme:\n colors:\n primary: \"%s\"\n" . }}
{{ end }}
{{ end }}
{{ end }}
{{/* ═══════════════════════════════════════════════════════════════════════════
MODE-SPECIFIC COLOR OVERRIDES
New feature: hugoblox.theme.colors_light / colors_dark
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $colors_light := $hb_theme.colors_light | default dict }}
{{ $colors_dark := $hb_theme.colors_dark | default dict }}
{{/* ═══════════════════════════════════════════════════════════════════════════
SEMANTIC UI OVERRIDES
New feature: hugoblox.theme.ui
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $ui := $hb_theme.ui | default dict }}
{{/* ═══════════════════════════════════════════════════════════════════════════
TYPOGRAPHY
Priority: hugoblox.typography → appearance.font (legacy)
font: "sans" (Inter, default) | "serif" (Roboto Slab) | "native" (system fonts)
size: "sm" | "md" | "lg" | "xl" (maps to base font size)
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $hb_typography := $hb.typography | default dict }}
{{ $font := "sans" }}
{{ $font_size := "md" }}
{{ with $hb_typography.font }}
{{ $font = . }}
{{ else }}
{{/* Check legacy appearance.font */}}
{{ with $appearance.font }}
{{ $font = . }}
{{ warnf "DEPRECATED: 'appearance.font' in config/_default/params.yaml is deprecated. Please move to:\n\nhugoblox:\n typography:\n font: \"%s\"\n" . }}
{{ end }}
{{ end }}
{{ with $hb_typography.size }}
{{ $font_size = . }}
{{ end }}
{{ $typography := dict "font" $font "size" $font_size }}
{{/* ═══════════════════════════════════════════════════════════════════════════
LAYOUT TOKENS
New feature: hugoblox.layout
radius: "none" | "sm" | "md" | "lg" | "full"
spacing: "compact" | "comfortable" | "spacious"
═══════════════════════════════════════════════════════════════════════════ */}}
{{ $hb_layout := $hb.layout | default dict }}
{{ $radius := $hb_layout.radius | default "md" }}
{{ $spacing := $hb_layout.spacing | default "comfortable" }}
{{ $layout := dict "radius" $radius "spacing" $spacing }}
{{/* Return theme config dict */}}
{{ return dict
"mode" $mode
"light" $light_pack
"dark" $dark_pack
"colors" $colors
"colors_light" $colors_light
"colors_dark" $colors_dark
"ui" $ui
"typography" $typography
"layout" $layout
}}
@@ -0,0 +1,59 @@
{{/*
HugoBlox Theme Engine: Loads and validates a HugoBlox theme pack
Input: Theme pack name (string), e.g. "minimal" or "acme/corporate"
Returns: The theme data object (type, light, dark, etc.) or nil if invalid
Validates:
- Theme exists in data/themes/
- Required 'hugoblox' wrapper is present
- Required 'meta.format' starts with 'hugoblox-theme@'
*/}}
{{ $pack_name := . }}
{{ $result := dict }}
{{/* Parse vendor/name format */}}
{{ $vendor := "" }}
{{ $name := $pack_name }}
{{ if strings.Contains $pack_name "/" }}
{{ $parts := split $pack_name "/" }}
{{ $vendor = index $parts 0 }}
{{ $name = index $parts 1 }}
{{ end }}
{{/* Load raw theme data */}}
{{ $raw_theme := dict }}
{{ if $vendor }}
{{/* Third-party theme: data/themes/vendor/name.yaml */}}
{{ $vendor_themes := index site.Data.themes $vendor }}
{{ if $vendor_themes }}
{{ $raw_theme = index $vendor_themes $name }}
{{ end }}
{{ else }}
{{/* Built-in or user theme: data/themes/name.yaml */}}
{{ $raw_theme = index site.Data.themes $name }}
{{ end }}
{{/* Validate theme structure */}}
{{ if not $raw_theme }}
{{ warnf "THEME ERROR: Theme pack '%s' not found in data/themes/" $pack_name }}
{{ else if not $raw_theme.hugoblox }}
{{ warnf "THEME ERROR: Theme '%s' is not a valid HugoBlox theme (missing 'hugoblox' key)" $pack_name }}
{{ else }}
{{ $theme := $raw_theme.hugoblox }}
{{/* Validate format version */}}
{{ $format := $theme.meta.format | default "" }}
{{ if not (hasPrefix $format "hugoblox-theme@") }}
{{ warnf "THEME ERROR: Theme '%s' has invalid format '%s'. Expected 'hugoblox-theme@1'" $pack_name $format }}
{{ end }}
{{/* Return the validated theme data (without the hugoblox wrapper) */}}
{{ $result = $theme }}
{{ end }}
{{ return $result }}
@@ -0,0 +1,540 @@
{{- $ctx := . -}}
{{- $hbxVerification := partial "functions/hbx_verify.html" $ctx -}}
{{- if ne $hbxVerification "verified" -}}
{{- errorf "Install HugoBlox to use the Notebook shortcode at: %s" $ctx.Position -}}
{{- end -}}
{{- $shortcodeName := $ctx.Name | default "notebook" -}}
{{- $src := "" -}}
{{- if $ctx.IsNamedParams -}}
{{- $src = $ctx.Get "src" | default ($ctx.Get "file") -}}
{{- else -}}
{{- $src = $ctx.Get 0 -}}
{{- end -}}
{{- if not $src -}}
{{- errorf "%s shortcode: provide a notebook path via positional argument or src=..." $shortcodeName -}}
{{- end -}}
{{- $displayName := $src -}}
{{- with urls.Parse $src -}}
{{- if .Path -}}
{{- $displayName = path.Base .Path -}}
{{- end -}}
{{- end -}}
{{- $title := $ctx.Get "title" | default $displayName -}}
{{- $languageOverride := $ctx.Get "language" -}}
{{- $showCode := $ctx.Get "show_code" | default true -}}
{{- $showMarkdown := $ctx.Get "show_markdown" | default true -}}
{{- $showOutputs := $ctx.Get "show_outputs" | default true -}}
{{- $showMetadata := $ctx.Get "show_metadata" | default false -}}
{{- $lineNumbers := $ctx.Get "line_numbers" | default false -}}
{{- $dense := $ctx.Get "dense" | default false -}}
{{- $maxOutputHeight := $ctx.Get "max_output_height" | default "26rem" -}}
{{- $sourceURL := $ctx.Get "source_url" -}}
{{- $showDownload := $ctx.Get "show_download" | default true -}}
{{- $downloadLabel := $ctx.Get "download_label" | default "Download notebook" -}}
{{- $emptyMessage := $ctx.Get "empty_message" | default "Notebook is empty or hidden by the current view options." -}}
{{- $id := delimit (slice "hb-notebook" (partial "functions/uid.html" $ctx)) "-" -}}
{{- $isRemote := and $src (ne (urls.Parse $src).Scheme "") -}}
{{- $normalizedSrc := $src -}}
{{- if not $isRemote -}}
{{- $normalizedSrc = strings.TrimPrefix "./" $normalizedSrc -}}
{{- $normalizedSrc = strings.TrimPrefix "/" $normalizedSrc -}}
{{- if $normalizedSrc -}}
{{- $normalizedSrc = path.Clean $normalizedSrc -}}
{{- end -}}
{{- if eq $normalizedSrc "." -}}
{{- $normalizedSrc = "" -}}
{{- end -}}
{{- if strings.HasPrefix $normalizedSrc ".." -}}
{{- errorf "%s shortcode: refusing to read outside the project for %q" $shortcodeName $src -}}
{{- end -}}
{{- end -}}
{{- $notebookContent := "" -}}
{{- $resource := dict -}}
{{- $resourceFound := false -}}
{{- if $isRemote -}}
{{- $remote := try (resources.GetRemote $src) -}}
{{- if $remote.Err -}}
{{- errorf "%s shortcode: unable to fetch remote notebook %q (%s)" $shortcodeName $src $remote.Err -}}
{{- else -}}
{{- $resource = $remote.Value -}}
{{- $resourceFound = true -}}
{{- $notebookContent = $resource.Content -}}
{{- if not $sourceURL -}}
{{- $sourceURL = $src -}}
{{- end -}}
{{- end -}}
{{- else -}}
{{- with $ctx.Page -}}
{{- with .Resources -}}
{{- with .GetMatch $normalizedSrc -}}
{{- $resource = . -}}
{{- $resourceFound = true -}}
{{- else -}}
{{- if not (strings.Contains $normalizedSrc "/") -}}
{{- with .GetMatch (printf "**/%s" $normalizedSrc) -}}
{{- $resource = . -}}
{{- $resourceFound = true -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if $resourceFound -}}
{{- $notebookContent = $resource.Content -}}
{{- if not $sourceURL -}}
{{- $sourceURL = $resource.RelPermalink -}}
{{- end -}}
{{- end -}}
{{- if not $notebookContent -}}
{{- $searchPaths := slice $normalizedSrc -}}
{{- $pageDir := "" -}}
{{- with $ctx.Page.File -}}
{{- $pageDir = path.Dir .Path -}}
{{- end -}}
{{- if $pageDir -}}
{{- $searchPaths = $searchPaths | append (path.Join $pageDir $normalizedSrc) -}}
{{- if not (strings.HasPrefix $normalizedSrc "content/") -}}
{{- $searchPaths = $searchPaths | append (printf "content/%s" (path.Join $pageDir $normalizedSrc)) -}}
{{- end -}}
{{- end -}}
{{- if not (strings.HasPrefix $normalizedSrc "content/") -}}
{{- $searchPaths = $searchPaths | append (printf "content/%s" $normalizedSrc) -}}
{{- end -}}
{{- if not (strings.HasPrefix $normalizedSrc "assets/") -}}
{{- $searchPaths = $searchPaths | append (printf "assets/%s" $normalizedSrc) -}}
{{- end -}}
{{- if not (strings.HasPrefix $normalizedSrc "static/") -}}
{{- $searchPaths = $searchPaths | append (printf "static/%s" $normalizedSrc) -}}
{{- end -}}
{{- range $candidate := $searchPaths }}
{{- if not $notebookContent -}}
{{- $fileAttempt := try (readFile $candidate) -}}
{{- if not $fileAttempt.Err -}}
{{- $notebookContent = $fileAttempt.Value -}}
{{- if and (strings.HasPrefix $candidate "static/") (not $sourceURL) -}}
{{- $public := strings.TrimPrefix $candidate "static" -}}
{{- if not (strings.HasPrefix $public "/") -}}
{{- $public = printf "/%s" $public -}}
{{- end -}}
{{- $sourceURL = relLangURL $public -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $notebookContent -}}
{{- $pagePath := "" -}}
{{- with $ctx.Page.File -}}
{{- $pagePath = .Path -}}
{{- end -}}
{{- errorf "%s shortcode: notebook %q could not be located relative to %s. Please check that you have removed \\.ipynb$ from ignoreFiles in your config/_default/hugo.yaml." $shortcodeName $src $pagePath -}}
{{- end -}}
{{- $parsed := dict -}}
{{- $parsedResult := try (transform.Unmarshal $notebookContent) -}}
{{- if $parsedResult.Err -}}
{{- errorf "%s shortcode: %q is not valid notebook JSON (%s)" $shortcodeName $src $parsedResult.Err -}}
{{- else -}}
{{- $parsed = $parsedResult.Value -}}
{{- end -}}
{{- $cells := slice -}}
{{- with index $parsed "cells" -}}
{{- if reflect.IsSlice . -}}
{{- $cells = . -}}
{{- end -}}
{{- end -}}
{{- $metadata := index $parsed "metadata" | default dict -}}
{{- $language := $languageOverride -}}
{{- if not $language -}}
{{- with index $metadata "language_info" -}}
{{- with index . "name" -}}
{{- $language = printf "%v" . -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $language -}}
{{- $language = "python" -}}
{{- end -}}
{{- $language = lower $language -}}
{{- $languageVersion := "" -}}
{{- with index $metadata "language_info" -}}
{{- with index . "version" -}}
{{- $languageVersion = printf "%v" . -}}
{{- end -}}
{{- end -}}
{{- $kernelDisplay := "" -}}
{{- with index $metadata "kernelspec" -}}
{{- with index . "display_name" -}}
{{- $kernelDisplay = printf "%v" . -}}
{{- end -}}
{{- end -}}
{{- $kernelName := "" -}}
{{- with index $metadata "kernelspec" -}}
{{- with index . "name" -}}
{{- $kernelName = printf "%v" . -}}
{{- end -}}
{{- end -}}
{{- $nbformat := "" -}}
{{- with index $parsed "nbformat" -}}
{{- $nbformat = printf "%v" . -}}
{{- end -}}
{{- with index $parsed "nbformat_minor" -}}
{{- if $nbformat -}}
{{- $nbformat = printf "%s.%v" $nbformat . -}}
{{- else -}}
{{- $nbformat = printf "%v" . -}}
{{- end -}}
{{- end -}}
{{- $cellCount := len $cells -}}
{{- $containerClass := "hb-notebook not-prose" -}}
{{- if $dense -}}
{{- $containerClass = printf "%s %s" $containerClass "hb-notebook--dense" -}}
{{- end -}}
{{- $subtitleParts := slice -}}
{{- if $language -}}
{{- $subtitleParts = $subtitleParts | append (strings.Title $language) -}}
{{- end -}}
{{- if $kernelDisplay -}}
{{- $subtitleParts = $subtitleParts | append (printf "Kernel: %s" $kernelDisplay) -}}
{{- end -}}
{{- if $nbformat -}}
{{- $subtitleParts = $subtitleParts | append (printf "nbformat %s" $nbformat) -}}
{{- end -}}
{{- if gt $cellCount 0 -}}
{{- $subtitleParts = $subtitleParts | append (printf "%d cells" $cellCount) -}}
{{- end -}}
{{- $metaItems := slice -}}
{{- $metaItems = $metaItems | append (dict "label" "Language" "value" (strings.Title $language)) -}}
{{- if $languageVersion -}}
{{- $metaItems = $metaItems | append (dict "label" "Version" "value" $languageVersion) -}}
{{- end -}}
{{- if or $kernelDisplay $kernelName -}}
{{- $metaItems = $metaItems | append (dict "label" "Kernel" "value" (strings.TrimSpace (printf "%s %s" $kernelDisplay $kernelName))) -}}
{{- end -}}
{{- if $nbformat -}}
{{- $metaItems = $metaItems | append (dict "label" "nbformat" "value" $nbformat) -}}
{{- end -}}
{{- with index $metadata "authors" -}}
{{- $authors := slice -}}
{{- if reflect.IsSlice . -}}
{{- range . -}}
{{- $name := "" -}}
{{- with index . "name" -}}
{{- $name = printf "%v" . -}}
{{- end -}}
{{- if not $name -}}
{{- $name = printf "%v" . -}}
{{- end -}}
{{- if $name -}}
{{- $authors = $authors | append $name -}}
{{- end -}}
{{- end -}}
{{- else -}}
{{- $authors = $authors | append (printf "%v" .) -}}
{{- end -}}
{{- if gt (len $authors) 0 -}}
{{- $metaItems = $metaItems | append (dict "label" "Authors" "value" (delimit $authors ", ")) -}}
{{- end -}}
{{- end -}}
{{- $sourceURLIsRemote := false -}}
{{- if $sourceURL -}}
{{- $sourceURLIsRemote = ne (urls.Parse $sourceURL).Scheme "" -}}
{{- if not $sourceURLIsRemote -}}
{{- if not (strings.HasPrefix $sourceURL "/") -}}
{{- $sourceURL = printf "/%s" $sourceURL -}}
{{- end -}}
{{- $sourceURL = relLangURL $sourceURL -}}
{{- end -}}
{{- end -}}
{{- $styleAttr := printf "--hb-notebook-output-max-height:%s;" $maxOutputHeight | safeCSS -}}
<div id="{{ $id }}" class="{{ $containerClass }}" data-hb-component="notebook" aria-label="Notebook {{ $title | plainify }}" style="{{ $styleAttr }}">
<div class="hb-notebook-header">
<div class="hb-notebook-heading">
<p class="hb-notebook-title">{{ $title }}</p>
{{- with $subtitleParts }}
<p class="hb-notebook-subtitle">{{ delimit . " · " }}</p>
{{- end }}
</div>
{{- if and $showDownload $sourceURL }}
<a class="hb-notebook-download" href="{{ $sourceURL | safeURL }}" {{ if $sourceURLIsRemote }}target="_blank" rel="noopener noreferrer"{{ else }}download{{ end }}>
{{ partial "functions/get_icon" (dict "name" "arrow-down-tray" "attributes" "class=\"w-4 h-4\"") }}
<span>{{ $downloadLabel }}</span>
</a>
{{- end }}
</div>
{{- if and $showMetadata (gt (len $metaItems) 0) }}
<dl class="hb-notebook-metadata">
{{- range $metaItems }}
{{- if .value }}
<div>
<dt>{{ .label }}</dt>
<dd>{{ .value }}</dd>
</div>
{{- end }}
{{- end }}
</dl>
{{- end }}
{{- $visibleScratch := newScratch -}}
{{- $visibleScratch.Set "visible" 0 -}}
<div class="hb-notebook-body">
{{- range $cells }}
{{- $cellType := index . "cell_type" | default "raw" -}}
{{- $cellMetadata := index . "metadata" | default dict -}}
{{- $cellTags := slice -}}
{{- with index $cellMetadata "tags" -}}
{{- if reflect.IsSlice . -}}
{{- range . -}}
{{- $cellTags = $cellTags | append (printf "%v" .) -}}
{{- end -}}
{{- else -}}
{{- $cellTags = $cellTags | append (printf "%v" .) -}}
{{- end -}}
{{- end -}}
{{- $executionCount := "" -}}
{{- with index . "execution_count" -}}
{{- $executionCount = printf "%v" . -}}
{{- end -}}
{{- $outputs := slice -}}
{{- with index . "outputs" -}}
{{- if reflect.IsSlice . -}}
{{- $outputs = . -}}
{{- end -}}
{{- end -}}
{{- $shouldRender := or
(and (eq $cellType "markdown") $showMarkdown)
(and (eq $cellType "raw") $showMarkdown)
(and (eq $cellType "code") (or $showCode (and $showOutputs (gt (len $outputs) 0))))
-}}
{{- if $shouldRender }}
{{- $visibleScratch.Add "visible" 1 -}}
<article class="hb-notebook-cell {{ cond (eq $cellType "code") "hb-notebook-cell--code" (cond (eq $cellType "markdown") "hb-notebook-cell--markdown" "hb-notebook-cell--raw") }}" data-cell-type="{{ $cellType }}">
<header class="hb-notebook-cell-header">
<span class="hb-notebook-pill">
{{- if eq $cellType "code" -}}
{{- printf "In [%s]" (cond $executionCount $executionCount " ") -}}
{{- else -}}
{{- strings.Title $cellType -}}
{{- end -}}
</span>
{{- if gt (len $cellTags) 0 }}
<div class="hb-notebook-tags">
{{- range $cellTags }}
<span>{{ . }}</span>
{{- end }}
</div>
{{- end }}
</header>
{{- if eq $cellType "markdown" }}
{{- $source := index . "source" -}}
{{- $sourceContent := "" -}}
{{- if reflect.IsSlice $source -}}
{{- $sourceContent = delimit $source "" -}}
{{- else if $source -}}
{{- $sourceContent = printf "%v" $source -}}
{{- end -}}
{{- $sourceContent = replace $sourceContent "\r\n" "\n" -}}
{{- if $sourceContent }}
<div class="hb-notebook-markdown prose dark:prose-invert">
{{ $sourceContent | markdownify }}
</div>
{{- end -}}
{{- else if eq $cellType "raw" }}
{{- $raw := index . "source" -}}
{{- $rawContent := "" -}}
{{- if reflect.IsSlice $raw -}}
{{- $rawContent = delimit $raw "" -}}
{{- else if $raw -}}
{{- $rawContent = printf "%v" $raw -}}
{{- end -}}
{{- if $rawContent }}
<pre class="hb-notebook-raw">{{ $rawContent | htmlEscape }}</pre>
{{- end -}}
{{- else if eq $cellType "code" }}
{{- $source := index . "source" -}}
{{- $sourceContent := "" -}}
{{- if reflect.IsSlice $source -}}
{{- $sourceContent = delimit $source "" -}}
{{- else if $source -}}
{{- $sourceContent = printf "%v" $source -}}
{{- end -}}
{{- $sourceContent = replace $sourceContent "\r\n" "\n" -}}
{{- $sourceContent = strings.TrimSuffix "\n" $sourceContent -}}
{{- $sourceContent = strings.TrimSuffix "\r" $sourceContent -}}
{{- if and $showCode $sourceContent }}
<div class="hb-notebook-code" data-language="{{ $language }}">
{{- $options := "" -}}
{{- if $lineNumbers -}}
{{- $options = "linenos=table" -}}
{{- end -}}
{{- $hl := highlight $sourceContent $language $options -}}
{{- if $hl -}}
{{ $hl | safeHTML }}
{{- else -}}
<pre><code>{{ $sourceContent | htmlEscape }}</code></pre>
{{- end -}}
</div>
{{- end -}}
{{- if and $showOutputs (gt (len $outputs) 0) }}
<div class="hb-notebook-outputs">
{{- range $outputs }}
{{- $outputType := index . "output_type" | default "" -}}
{{- if eq $outputType "stream" }}
{{- $text := index . "text" -}}
{{- $textContent := "" -}}
{{- if reflect.IsSlice $text -}}
{{- $textContent = delimit $text "" -}}
{{- else if $text -}}
{{- $textContent = printf "%v" $text -}}
{{- end -}}
<pre class="hb-notebook-output hb-notebook-output--stream"><code>{{ $textContent | htmlEscape }}</code></pre>
{{- else if eq $outputType "error" }}
{{- $ename := index . "ename" | default "" -}}
{{- $evalue := index . "evalue" | default "" -}}
{{- $trace := index . "traceback" | default slice -}}
{{- $traceContent := "" -}}
{{- if reflect.IsSlice $trace -}}
{{- $traceContent = delimit $trace "\n" -}}
{{- else if $trace -}}
{{- $traceContent = printf "%v" $trace -}}
{{- end -}}
<div class="hb-notebook-output hb-notebook-output--error">
<strong>{{ printf "%s: %s" $ename $evalue }}</strong>
{{- if $traceContent }}
<pre><code>{{ $traceContent | htmlEscape }}</code></pre>
{{- end -}}
</div>
{{- else }}
{{- $data := index . "data" | default dict -}}
{{- $outputScratch := newScratch -}}
{{- $outputScratch.Set "rendered" false -}}
{{- with index $data "text/html" }}
{{- $htmlPayload := "" -}}
{{- if reflect.IsSlice . -}}
{{- $htmlPayload = delimit . "" -}}
{{- else -}}
{{- $htmlPayload = printf "%v" . -}}
{{- end -}}
{{- if $htmlPayload }}
<div class="hb-notebook-output hb-notebook-output--html">{{ $htmlPayload | safeHTML }}</div>
{{- $outputScratch.Set "rendered" true -}}
{{- end -}}
{{- end }}
{{- with index $data "text/markdown" }}
{{- $markdownPayload := "" -}}
{{- if reflect.IsSlice . -}}
{{- $markdownPayload = delimit . "" -}}
{{- else -}}
{{- $markdownPayload = printf "%v" . -}}
{{- end -}}
{{- if $markdownPayload }}
<div class="hb-notebook-output hb-notebook-output--markdown prose dark:prose-invert">
{{ $markdownPayload | markdownify }}
</div>
{{- $outputScratch.Set "rendered" true -}}
{{- end -}}
{{- end }}
{{- with index $data "text/latex" }}
{{- $latexPayload := "" -}}
{{- if reflect.IsSlice . -}}
{{- $latexPayload = delimit . "" -}}
{{- else -}}
{{- $latexPayload = printf "%v" . -}}
{{- end -}}
{{- if $latexPayload }}
<div class="hb-notebook-output hb-notebook-output--latex">{{ printf "$$%s$$" $latexPayload | safeHTML }}</div>
{{- $outputScratch.Set "rendered" true -}}
{{- end -}}
{{- end }}
{{- with index $data "image/png" }}
{{- $pngPayload := printf "%v" . -}}
{{- if $pngPayload }}
<div class="hb-notebook-output hb-notebook-output--image">
<img src="data:image/png;base64,{{ $pngPayload }}" alt="Notebook output image" loading="lazy" decoding="async" />
</div>
{{- $outputScratch.Set "rendered" true -}}
{{- end -}}
{{- end }}
{{- with index $data "image/jpeg" }}
{{- $jpegPayload := printf "%v" . -}}
{{- if $jpegPayload }}
<div class="hb-notebook-output hb-notebook-output--image">
<img src="data:image/jpeg;base64,{{ $jpegPayload }}" alt="Notebook output image" loading="lazy" decoding="async" />
</div>
{{- $outputScratch.Set "rendered" true -}}
{{- end -}}
{{- end }}
{{- with index $data "image/svg+xml" }}
{{- $svgPayload := "" -}}
{{- if reflect.IsSlice . -}}
{{- $svgPayload = delimit . "" -}}
{{- else -}}
{{- $svgPayload = printf "%v" . -}}
{{- end -}}
{{- if $svgPayload }}
<div class="hb-notebook-output hb-notebook-output--image">{{ $svgPayload | safeHTML }}</div>
{{- $outputScratch.Set "rendered" true -}}
{{- end -}}
{{- end }}
{{- with or (index $data "application/vnd.plotly.v1+json") (index $data "application/json") }}
{{- $jsonFormatted := . | jsonify (dict "indent" " ") -}}
<pre class="hb-notebook-output hb-notebook-output--json"><code>{{ $jsonFormatted }}</code></pre>
{{- $outputScratch.Set "rendered" true -}}
{{- end }}
{{- if not ($outputScratch.Get "rendered") }}
{{- with index $data "text/plain" }}
{{- $textPayload := "" -}}
{{- if reflect.IsSlice . -}}
{{- $textPayload = delimit . "" -}}
{{- else -}}
{{- $textPayload = printf "%v" . -}}
{{- end -}}
{{- if $textPayload }}
<pre class="hb-notebook-output hb-notebook-output--text"><code>{{ $textPayload | htmlEscape }}</code></pre>
{{- $outputScratch.Set "rendered" true -}}
{{- end -}}
{{- end }}
{{- end }}
{{- if not ($outputScratch.Get "rendered") }}
<pre class="hb-notebook-output hb-notebook-output--json"><code>{{ $data | jsonify | htmlEscape }}</code></pre>
{{- end }}
{{- end }}
{{- end }}
</div>
{{- end }}
{{- end }}
</article>
{{- end }}
{{- end }}
</div>
{{- if eq ($visibleScratch.Get "visible") 0 }}
<div class="hb-notebook-empty">{{ $emptyMessage }}</div>
{{- end }}
<div class="sr-only" data-hb-component="notebook" data-hb-version="1.0" data-hb-license="MIT">
Powered by Hugo Blox Builder - https://github.com/HugoBlox/hugo-blox-builder
</div>
</div>
@@ -58,7 +58,8 @@
{{ $mesh_style := $gradient_mesh.style | default "orbs" }}
{{ $mesh_animation_raw := $gradient_mesh.animation | default "pulse" }}
{{ $mesh_intensity := $gradient_mesh.intensity | default "subtle" }}
{{ $mesh_colors := $gradient_mesh.colors | default (slice "primary-500/20" "primary-600/20") }}
{{/* Auto-theme: Mix Primary and Secondary brand colors for dynamic personalization */}}
{{ $mesh_colors := $gradient_mesh.colors | default (slice "primary-500/20" "secondary-500/20") }}
{{/* Map animation names to CSS classes */}}
{{ $mesh_animation := "" }}
@@ -1,79 +1,150 @@
{{/* HugoBlox Theme Engine: Generates CSS Variables for Light and Dark modes */}}
{{/* Inputs: .light (default 'minimal') and .dark (default 'dark') */}}
{{/*
HugoBlox Theme Engine: Generates CSS Variables for Light and Dark modes
Input: Theme config dict from get_theme_config helper:
- mode: 'light' | 'dark' | 'system'
- light: Light theme pack name
- dark: Dark theme pack name
- colors: Global color overrides { primary, secondary, neutral }
- colors_light: Light-mode specific color overrides
- colors_dark: Dark-mode specific color overrides
- ui: Semantic UI overrides
*/}}
{{ $appearance := site.Params.appearance | default dict }}
{{ $theme_settings := $appearance.theme | default dict }}
{{ $light_theme_name := $theme_settings.light | default "default" }}
{{ $dark_theme_name := $theme_settings.dark | default "default" }}
{{ $global_colors := $theme_settings.colors | default dict }}
{{ $primary_override := $global_colors.primary | default $appearance.color }}
{{ $secondary_override := $global_colors.secondary }}
{{ $config := . }}
{{ $light_theme_name := $config.light | default "default" }}
{{ $dark_theme_name := $config.dark | default "default" }}
{{ $global_colors := $config.colors | default dict }}
{{ $colors_light := $config.colors_light | default dict }}
{{ $colors_dark := $config.colors_dark | default dict }}
{{ $ui_overrides := $config.ui | default dict }}
{{/* Load Theme Data */}}
{{/* Load Theme Data using the new loader */}}
{{ if not site.Data.themes }}
{{ warnf "THEME ENGINE ERROR: site.Data.themes is empty! Check modules/blox-tailwind/data/themes/" }}
{{ end }}
{{ $light_theme := index site.Data.themes $light_theme_name }}
{{ $dark_theme := index site.Data.themes $dark_theme_name }}
{{ $light_theme := partial "functions/load_theme_pack" $light_theme_name }}
{{ $dark_theme := partial "functions/load_theme_pack" $dark_theme_name }}
{{/* Fallback for missing themes */}}
{{ if not $light_theme }}
{{ warnf "THEME ENGINE WARNING: Light theme '%s' not found. Defaulting to 'default'." $light_theme_name }}
{{ $light_theme = site.Data.themes.default }}
{{ warnf "THEME ENGINE WARNING: Light theme '%s' not found or invalid. Defaulting to 'default'." $light_theme_name }}
{{ $light_theme = partial "functions/load_theme_pack" "default" }}
{{ end }}
{{ if not $dark_theme }}
{{ warnf "THEME ENGINE WARNING: Dark theme '%s' not found. Defaulting to 'default'." $dark_theme_name }}
{{ $dark_theme = site.Data.themes.default }}
{{ warnf "THEME ENGINE WARNING: Dark theme '%s' not found or invalid. Defaulting to 'default'." $dark_theme_name }}
{{ $dark_theme = partial "functions/load_theme_pack" "default" }}
{{ end }}
{{/* Resolve effective light/dark mode objects */}}
{{/* Resolve effective light/dark mode objects from theme files */}}
{{ $light_base := $light_theme.light | default $light_theme }}
{{ $dark_base := $dark_theme.dark | default $dark_theme }}
{{/* Apply global color overrides (palette or hex) */}}
{{ if or $primary_override $secondary_override }}
{{ if $light_base }}
{{ $light_colors := merge (dict) ($light_base.colors | default dict) }}
{{ if $primary_override }}
{{ $light_colors = merge $light_colors (dict "primary" $primary_override) }}
{{ end }}
{{ if $secondary_override }}
{{ $light_colors = merge $light_colors (dict "secondary" $secondary_override) }}
{{ end }}
{{ $light_base = merge $light_base (dict "colors" $light_colors) }}
{{/* ═══════════════════════════════════════════════════════════════════════════
APPLY COLOR OVERRIDES
Priority: colors_light/colors_dark → colors (global) → theme pack defaults
═══════════════════════════════════════════════════════════════════════════ */}}
{{/* Apply global color overrides to LIGHT mode */}}
{{ if $light_base }}
{{ $light_colors := merge (dict) ($light_base.colors | default dict) }}
{{/* Global overrides */}}
{{ with $global_colors.primary }}
{{ $light_colors = merge $light_colors (dict "primary" .) }}
{{ end }}
{{ if $dark_base }}
{{ $dark_colors := merge (dict) ($dark_base.colors | default dict) }}
{{ if $primary_override }}
{{ $dark_colors = merge $dark_colors (dict "primary" $primary_override) }}
{{ with $global_colors.secondary }}
{{ $light_colors = merge $light_colors (dict "secondary" .) }}
{{ end }}
{{ with $global_colors.neutral }}
{{ $light_colors = merge $light_colors (dict "neutral" .) }}
{{ end }}
{{/* Mode-specific overrides (highest priority) */}}
{{ with $colors_light.primary }}
{{ $light_colors = merge $light_colors (dict "primary" .) }}
{{ end }}
{{ with $colors_light.secondary }}
{{ $light_colors = merge $light_colors (dict "secondary" .) }}
{{ end }}
{{ with $colors_light.neutral }}
{{ $light_colors = merge $light_colors (dict "neutral" .) }}
{{ end }}
{{ $light_base = merge $light_base (dict "colors" $light_colors) }}
{{/* Apply UI overrides */}}
{{ if $ui_overrides }}
{{ $light_ui := merge (dict) ($light_base.ui | default dict) }}
{{ range $key, $value := $ui_overrides }}
{{ $light_ui = merge $light_ui (dict $key $value) }}
{{ end }}
{{ if $secondary_override }}
{{ $dark_colors = merge $dark_colors (dict "secondary" $secondary_override) }}
{{ end }}
{{ $dark_base = merge $dark_base (dict "colors" $dark_colors) }}
{{ $light_base = merge $light_base (dict "ui" $light_ui) }}
{{ end }}
{{ end }}
{{/* Apply global color overrides to DARK mode */}}
{{ if $dark_base }}
{{ $dark_colors := merge (dict) ($dark_base.colors | default dict) }}
{{/* Global overrides */}}
{{ with $global_colors.primary }}
{{ $dark_colors = merge $dark_colors (dict "primary" .) }}
{{ end }}
{{ with $global_colors.secondary }}
{{ $dark_colors = merge $dark_colors (dict "secondary" .) }}
{{ end }}
{{ with $global_colors.neutral }}
{{ $dark_colors = merge $dark_colors (dict "neutral" .) }}
{{ end }}
{{/* Mode-specific overrides (highest priority) */}}
{{ with $colors_dark.primary }}
{{ $dark_colors = merge $dark_colors (dict "primary" .) }}
{{ end }}
{{ with $colors_dark.secondary }}
{{ $dark_colors = merge $dark_colors (dict "secondary" .) }}
{{ end }}
{{ with $colors_dark.neutral }}
{{ $dark_colors = merge $dark_colors (dict "neutral" .) }}
{{ end }}
{{ $dark_base = merge $dark_base (dict "colors" $dark_colors) }}
{{/* Apply UI overrides */}}
{{ if $ui_overrides }}
{{ $dark_ui := merge (dict) ($dark_base.ui | default dict) }}
{{ range $key, $value := $ui_overrides }}
{{ $dark_ui = merge $dark_ui (dict $key $value) }}
{{ end }}
{{ $dark_base = merge $dark_base (dict "ui" $dark_ui) }}
{{ end }}
{{ end }}
{{/* ═══════════════════════════════════════════════════════════════════════════
GENERATE CSS OUTPUT
═══════════════════════════════════════════════════════════════════════════ */}}
<style>
/* stylelint-disable */
/* HugoBlox Theme Engine - Light: {{ $light_theme_name }}, Dark: {{ $dark_theme_name }} */
/* 1. Light Mode (Default :root) */
:root {
{{ if $light_base }}
/* Light Theme: {{ $light_theme_name }} */
{{ partial "functions/theme_variables" (dict "data" $light_base "is_dark" false) | safeCSS }}
{{ else }}
/* Failed to load data for {{ $light_theme_name }} */
/* ERROR: Failed to load light theme '{{ $light_theme_name }}' */
{{ end }}
}
/* 2. Dark Mode (.dark class) */
.dark {
{{ if $dark_base }}
/* Dark Theme: {{ $dark_theme_name }} */
{{ partial "functions/theme_variables" (dict "data" $dark_base "is_dark" true) | safeCSS }}
{{ else }}
/* Failed to load data for {{ $dark_theme_name }} */
/* ERROR: Failed to load dark theme '{{ $dark_theme_name }}' */
{{ end }}
}
/* stylelint-enable */
@@ -9,7 +9,8 @@
{{ end }}
{{ $author := partial "functions/get_author_name" $page }}
{{ $publisher := site.Params.marketing.seo.org_name | default site.Title }}
{{ $branding := partialCached "functions/get_branding" $page "branding" }}
{{ $publisher := $branding.organization }}
{{ $logo_url := partial "functions/get_logo_url" $page -}}
<script type="application/ld+json">
@@ -1,10 +1,11 @@
{{- $sharing_image := resources.GetMatch (path.Join "media" "sharing.*") -}}
{{- $branding := partialCached "functions/get_branding" . "branding" -}}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": {{site.Params.marketing.seo.site_type | default site.Params.marketing.seo.site_type}},
"@type": {{site.Params.marketing.seo.site_type | default "Organization"}},
"@id": {{site.BaseURL}},
"name": {{site.Params.marketing.seo.org_name | default site.Title}},
"name": {{$branding.organization}},
"logo": {{ partial "functions/get_logo_url" . }},
{{with $sharing_image}}"image": {{.Permalink}},{{end}}
{{ if (eq site.Params.marketing.seo.site_type "LocalBusiness") | and site.Params.marketing.seo.coordinates }}
@@ -4,7 +4,8 @@
{{/* Get author and publisher info */}}
{{ $author := partial "functions/get_author_name" $page }}
{{ $publisher := site.Params.marketing.seo.org_name | default site.Title }}
{{ $branding := partialCached "functions/get_branding" $page "branding" }}
{{ $publisher := $branding.organization }}
{{/* Extract question - prioritize explicit param, fallback to title */}}
{{ $question := $page.Params.question | default $page.Title }}
@@ -85,4 +86,3 @@
}
}
</script>
@@ -1,5 +1,6 @@
{{/* Display copyright license. */}}
{{ $branding := partialCached "functions/get_branding" . "branding" }}
{{ $copyright_license := .Params.copyright.license | default site.Params.footer.copyright.license }}
{{ $notice := .Params.copyright.notice | default site.Params.footer.copyright.notice }}
@@ -23,7 +24,10 @@
{{ with $notice }}
<p class="powered-by text-center">
{{ replace (replace . "{year}" now.Year) "{license}" (printf "<a href=\"%s\" rel=\"noopener noreferrer\" target=\"_blank\">CC %s 4.0</a>" $license_url (replace $cc_code "-" " " | upper)) | markdownify }}
{{ $processed := replace . "{year}" now.Year }}
{{ $processed = replace $processed "{name}" $branding.organization }}
{{ $processed = replace $processed "{license}" (printf "<a href=\"%s\" rel=\"noopener noreferrer\" target=\"_blank\">CC %s 4.0</a>" $license_url (replace $cc_code "-" " " | upper)) }}
{{ $processed | markdownify }}
</p>
{{ end }}
@@ -46,7 +50,9 @@
{{ with $notice }}
<p class="powered-by text-center">
{{ replace . "{year}" now.Year | markdownify }}
{{ $processed := replace . "{year}" now.Year }}
{{ $processed = replace $processed "{name}" $branding.organization }}
{{ $processed | markdownify }}
</p>
{{ end }}
@@ -27,15 +27,11 @@
{{/* Generate page description. */}}
{{ $desc := "" }}
{{ if .Params.summary }}
{{ $desc = .Params.summary }}
{{ else if .Params.abstract }}
{{ $desc = .Params.abstract }}
{{ else if .IsPage }}
{{ $desc = .Summary }}
{{ else if site.Params.marketing.seo.description }}
{{ $desc = partial "functions/get_summary" . }}
{{ if site.Params.marketing.seo.description | and (not $desc) }}
{{ $desc = site.Params.marketing.seo.description }}
{{ else }}
{{ end }}
{{ if not $desc }}
{{ $desc = $superuser_role }}
{{ end }}
<meta name="description" content="{{ $desc | plainify }}" />
@@ -45,19 +41,14 @@
{{ end }}
<link rel="alternate" hreflang="{{ site.LanguageCode | default "en-us" }}" href="{{ .Permalink }}" />
{{/* Hugo Blox Color Theme Initialization */}}
{{ $legacy_color := (lower site.Params.appearance.color) | default "blue" }}
{{/* 1. New Data-Driven System (Mix & Match) */}}
{{ if site.Params.appearance.theme }}
{{ partial "functions/theme_generator.html" (dict "light" site.Params.appearance.theme.light "dark" site.Params.appearance.theme.dark) }}
{{/* 2. Legacy CSS System Fallback */}}
{{ else if (fileExists (printf "assets/css/themes/%s.css" $legacy_color)) }}
{{ $theme_css := resources.Get (printf "css/themes/%s.css" $legacy_color) | minify }}
<link rel="stylesheet" href="{{ $theme_css.RelPermalink }}" />
{{ else }}
{{/* 3. Default Fallback */}}
{{ partial "functions/theme_generator.html" (dict "light" "default" "dark" "default") }}
{{/* Hugo Blox Theme Engine Initialization */}}
{{/* Uses get_theme_config helper for backward-compatible config resolution */}}
{{ $theme_config := partialCached "functions/get_theme_config" . "theme_config" }}
{{ partial "functions/theme_generator.html" $theme_config }}
{{/* Demo Theme Pack Styles (if enabled) */}}
{{ if site.Params.header.navbar.show_theme_picker }}
{{ partial "functions/demo_theme_styles.html" (dict "typography" $theme_config.typography "layout" $theme_config.layout) }}
{{ end }}
{{/* Tailwind CSS v4 Processing */}}
@@ -93,13 +84,30 @@
{{ partialCached "blox-analytics/index" . }}
{{/* RSS Feed */}}
{{ $branding := partialCached "functions/get_branding" . "branding" }}
{{ with .OutputFormats.Get "RSS" }}
<link rel="alternate" href="{{ .RelPermalink }}" type="application/rss+xml" title="{{ site.Title }}" />
<link rel="alternate" href="{{ .RelPermalink }}" type="application/rss+xml" title="{{ $branding.site_title }}" />
{{ end }}
{{/* Progressive Web App (PWA) Icon */}}
<link rel="icon" type="image/png" href="{{ (partial "functions/get_site_icon" 32).RelPermalink }}" />
<link rel="apple-touch-icon" type="image/png" href="{{ (partial "functions/get_site_icon" 180).RelPermalink }}" />
{{/* Progressive Web App (PWA) Icon - SVG preferred */}}
{{ $icon := partial "functions/get_site_icon" 32 }}
{{ with $icon.resource }}
<link rel="icon" type="{{ $icon.type }}" href="{{ .RelPermalink }}" />
{{ end }}
{{/* Apple Touch Icon - requires PNG */}}
{{ $icon_apple := partial "functions/get_site_icon" 180 }}
{{ if not $icon_apple.is_svg }}
{{ with $icon_apple.resource }}
<link rel="apple-touch-icon" type="image/png" href="{{ .RelPermalink }}" />
{{ end }}
{{ else }}
{{/* SVG icon - need PNG fallback for Apple */}}
{{ $icon_png := resources.Get "media/icon.png" }}
{{ with $icon_png }}
{{ $apple_icon := .Fill "180x180 Center" }}
<link rel="apple-touch-icon" type="image/png" href="{{ $apple_icon.RelPermalink }}" />
{{ end }}
{{ end }}
<link rel="canonical" href="{{ .Permalink }}" />
@@ -120,27 +128,33 @@
{{ $og_image = $sharing_image.Permalink }}
{{ else if $has_logo }}
{{/* !CACHED! Can safely cache this site logo variant */}}
{{ $og_image = (partialCached "functions/get_logo" (dict "constraint" "fit" "size" 300)).Permalink }}
{{ $logo_data := partialCached "functions/get_logo" (dict "constraint" "fit" "size" 300) "logo_og" }}
{{ with $logo_data.resource }}
{{ $og_image = .Permalink }}
{{ end }}
{{ $twitter_card = "summary" }}
{{ else }}
{{ $og_image = (partial "functions/get_site_icon" 512).Permalink }}
{{ $icon_og := partial "functions/get_site_icon" 512 }}
{{ with $icon_og.resource }}
{{ $og_image = .Permalink }}
{{ end }}
{{ $twitter_card = "summary" }}
{{ end }}
{{ $scr.Set "og_image" $og_image }}{{/* Set `og_image` globally for `rss.xml`. */}}
{{ $title := "" }}
{{ with .Params.seo.title }}
{{ $title = replace . "{brand}" site.Title }}
{{ $title = replace . "{brand}" $branding.site_title }}
{{ else }}
{{ $title = .Title | default site.Title }}
{{ if ne $title site.Title }}{{ $title = printf "%s | %s" $title site.Title }}{{ end }}
{{ $title = .Title | default $branding.site_title }}
{{ if ne $title $branding.site_title }}{{ $title = printf "%s | %s" $title $branding.site_title }}{{ end }}
{{ end }}
<meta property="twitter:card" content="{{ $twitter_card }}" />
{{ with site.Params.marketing.seo.twitter }}
<meta property="twitter:site" content="@{{ . }}" />
<meta property="twitter:creator" content="@{{ . }}" />
{{ end }}
<meta property="og:site_name" content="{{ site.Title }}" />
<meta property="og:site_name" content="{{ $branding.site_title }}" />
<meta property="og:url" content="{{ .Permalink }}" />
<meta property="og:title" content="{{ $title }}" />
<meta property="og:description" content="{{ $desc }}" />
@@ -169,26 +183,81 @@
<title>{{$title}}</title>
{{/* Load font theme */}}
{{/* Load font theme - uses typography config from get_theme_config */}}
{{/* Supported values: 'sans' (Inter, default), 'serif' (Roboto Slab), 'native' (system fonts) */}}
{{ $font_setting := $theme_config.typography.font | default "sans" }}
{{ $use_custom_font := true }}
{{ $font_family := "Inter var" }}
{{ $font_file := "" }}
{{ $font_type := "" }}
{{ if eq site.Params.appearance.font "serif" }}
{{ $font_file := "Inter.var.woff2" }}
{{ $font_type := "woff2" }}
{{ if eq $font_setting "native" }}
{{/* Native system font stack - no custom font loaded */}}
{{ $use_custom_font = false }}
{{ else if eq $font_setting "serif" }}
{{ $font_file = "RobotoSlab-VariableFont_wght.ttf" }}
{{ $font_type = "truetype" }}
{{else}}
{{ $font_file = "Inter.var.woff2" }}
{{ $font_type = "woff2" }}
{{end}}
{{ $font := resources.Get (printf "dist/font/%s" $font_file) }}
{{ end }}
{{/* Default case (sans or any unrecognized value) uses Inter */}}
{{/* Map size tokens to CSS custom properties */}}
{{ $font_size_base := "1rem" }}
{{ $font_size_scale := "1.25" }}
{{ if eq $theme_config.typography.size "sm" }}
{{ $font_size_base = "0.875rem" }}
{{ $font_size_scale = "1.2" }}
{{ else if eq $theme_config.typography.size "lg" }}
{{ $font_size_base = "1.125rem" }}
{{ $font_size_scale = "1.333" }}
{{ else if eq $theme_config.typography.size "xl" }}
{{ $font_size_base = "1.25rem" }}
{{ $font_size_scale = "1.414" }}
{{ end }}
{{/* Map radius tokens to CSS values */}}
{{ $radius_value := "0.5rem" }}
{{ if eq $theme_config.layout.radius "none" }}
{{ $radius_value = "0" }}
{{ else if eq $theme_config.layout.radius "sm" }}
{{ $radius_value = "0.25rem" }}
{{ else if eq $theme_config.layout.radius "lg" }}
{{ $radius_value = "0.75rem" }}
{{ else if eq $theme_config.layout.radius "full" }}
{{ $radius_value = "9999px" }}
{{ end }}
{{/* Map spacing tokens to CSS values */}}
{{ $spacing_base := "1rem" }}
{{ $spacing_section := "4rem" }}
{{ if eq $theme_config.layout.spacing "compact" }}
{{ $spacing_base = "0.75rem" }}
{{ $spacing_section = "2.5rem" }}
{{ else if eq $theme_config.layout.spacing "spacious" }}
{{ $spacing_base = "1.25rem" }}
{{ $spacing_section = "6rem" }}
{{ end }}
<style>
:root {
/* Typography tokens */
--hb-font-size-base: {{ $font_size_base }};
--hb-font-size-scale: {{ $font_size_scale }};
/* Layout tokens */
--hb-radius: {{ $radius_value }};
--hb-spacing-base: {{ $spacing_base }};
--hb-spacing-section: {{ $spacing_section }};
}
{{ if $use_custom_font }}
{{ $font_resource := resources.Get (printf "dist/font/%s" $font_file) }}
@font-face {
font-family: '{{$font_family}}';
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url({{ $font.RelPermalink }}) format({{$font_type}});
src: url({{ $font_resource.RelPermalink }}) format({{$font_type}});
}
{{ end }}
</style>
{{ if .Params.design.background.image.filename }}
@@ -122,7 +122,7 @@
</h3>
<p class="text-zinc-600 dark:text-zinc-400 text-base leading-relaxed line-clamp-3">
{{ ($item.Params.summary | default $item.Summary) | plainify | htmlUnescape | truncate 180 }}
{{ (partial "functions/get_summary" $item) | plainify | htmlUnescape | truncate 180 }}
</p>
{{ with $item.Params.content_meta.prerequisites }}
@@ -1,6 +1,6 @@
{{ $item := .item }}
{{ $page := .page }}
{{ $summary := $item.Params.summary | default $item.Params.description | default $item.Summary }}
{{ $summary := partial "functions/get_summary" $item }}
{{ $summary = $summary | page.RenderString }}
<article class="md:grid md:grid-cols-4 md:items-baseline">
<div class="md:col-span-3 group relative flex flex-col items-start">
@@ -15,7 +15,7 @@
datetime="{{ time.Format "2006-01-02" $item.Date }}">
{{ time.Format (site.Params.locale.date_format | default ":date_long") $item.Date }}
</time>
<p class="relative z-10 mt-2 text-sm text-zinc-600 dark:text-zinc-400">{{$summary}}</p>
<p class="relative z-10 mt-2 text-sm text-zinc-600 dark:text-zinc-400 line-clamp-3">{{$summary}}</p>
<div aria-hidden="true" class="relative z-10 mt-4 flex items-center text-sm font-medium text-primary-500">
{{ T "read_more" | default "Read more" }}
<svg aria-hidden="true" class="ml-1 h-4 w-4 stroke-current" fill="none" viewBox="0 0 16 16">
@@ -0,0 +1,42 @@
{{/*
HugoBlox Notebook Shortcode
Copyright (c) HugoBlox
Licensed under MIT WITH ATTRIBUTION REQUIRED:
- Keep this notice intact
- Preserve visible/hidden "Powered by Hugo Blox Builder" attribution
- See https://github.com/HugoBlox/hugo-blox-builder/blob/main/LICENSE.md
This shortcode relies on the Hugo Blox Builder theme ecosystem.
Docs: https://docs.hugoblox.com/
Render Jupyter/Quarto notebooks directly inside Markdown while keeping blocks editable.
Parameters
----------
src/file (required)
Notebook path. Supports page resources, assets/, static/, or remote URLs.
title (optional)
Heading override. Defaults to notebook filename.
language (optional)
Highlight language override. Defaults to notebook metadata or python.
show_code / show_markdown / show_outputs (optional, default true)
Toggle sections per cell type.
show_metadata (optional, default false)
Display kernel/language metadata badges.
line_numbers (optional, default false)
Render chroma line numbers for code cells.
max_output_height (optional, default "26rem")
CSS value for output scroll area height (supports rem/vh/calc()).
dense (optional, default false)
Compact spacing preset.
source_url / show_download / download_label (optional)
Override download link behavior.
Examples
--------
{{< notebook src="notebooks/analysis.ipynb" >}}
{{< notebook src="https://example.com/demo.ipynb" show_metadata=true line_numbers=true >}}
{{< notebook file="stats/notebook.ipynb" show_code=false show_outputs=true title="Model outputs" >}}
*/}}
{{ partial "functions/notebook/render" . }}
@@ -82,10 +82,13 @@
<p class="text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">
{{ .Params.answer | plainify | truncate 200 }}
</p>
{{ else if .Summary }}
<p class="text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">
{{ .Summary }}
</p>
{{ else }}
{{ $summary := partial "functions/get_summary" . }}
{{ if $summary }}
<p class="text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">
{{ $summary }}
</p>
{{ end }}
{{ end }}
<div class="flex flex-wrap items-center gap-3 text-sm text-gray-500 dark:text-gray-400">
@@ -3,8 +3,8 @@
# Hugo Documentation: https://gohugo.io/getting-started/configuration/#all-configuration-settings
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
# Website name
title: Hugo Academic CV Theme
# Website name (set in params.yaml under hugoblox.branding.name)
title: ''
# Website URL
baseURL: 'https://example.com/'
@@ -30,7 +30,7 @@ pagination:
enableEmoji: true
enableRobotsTXT: true
footnotereturnlinkcontents: <sup>^</sup>
ignoreFiles: [\.ipynb$, .ipynb_checkpoints$, \.Rmd$, \.Rmarkdown$, _cache$]
ignoreFiles: [".ipynb_checkpoints$", "\\.Rmd$", "\\.Rmarkdown$", "_cache$"]
disableAliases: true
outputs:
home: [HTML, RSS, headers, redirects, backlinks]
@@ -3,19 +3,64 @@
# Documentation: https://docs.hugoblox.com/
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
# Appearance
appearance:
mode: system
theme:
light: "default"
dark: "default"
# ==============================================================================
# HUGO BLOX CONFIGURATION
# ==============================================================================
hugoblox:
# ────────────────────────────────────────────────────────────────────────────
# BRANDING
# Your site's identity - used in navbar, footer, SEO, and copyright
# ────────────────────────────────────────────────────────────────────────────
branding:
# Display name shown in navbar and general site display
name: "Your Name"
# Optional: Browser tab title (defaults to name if not set)
# site_title: "Your Name | Research"
# Optional: Legal/copyright entity (defaults to name if not set)
# organization: "Your Full Legal Name"
# SEO
# ────────────────────────────────────────────────────────────────────────────
# THEME
# Controls colors and appearance
# ────────────────────────────────────────────────────────────────────────────
theme:
# Mode: 'light', 'dark', or 'system' (follows OS preference)
mode: system
# Theme pack: name from data/themes/ or { light: "x", dark: "y" } for mix-and-match
pack: "default"
# Optional: Override theme pack colors
# colors:
# primary: "indigo" # Tailwind palette name or hex (e.g., "#6366f1")
# secondary: "emerald"
# neutral: "stone" # Affects gray utilities (text-gray-*, bg-gray-*)
# ────────────────────────────────────────────────────────────────────────────
# TYPOGRAPHY
# Controls fonts and text sizing
# ────────────────────────────────────────────────────────────────────────────
typography:
# Font: 'sans' (Inter), 'serif' (Roboto Slab), or 'native' (system fonts)
font: "sans"
# Size: 'sm', 'md', 'lg', or 'xl' (affects base font size and scale)
size: "md"
# ────────────────────────────────────────────────────────────────────────────
# LAYOUT
# Controls spacing and visual structure
# ────────────────────────────────────────────────────────────────────────────
layout:
# Border radius: 'none', 'sm', 'md', 'lg', or 'full'
radius: "md"
# Spacing density: 'compact', 'comfortable', or 'spacious'
spacing: "comfortable"
# ==============================================================================
# SEO & MARKETING
# ==============================================================================
marketing:
seo:
site_type: Person
local_business_type: ''
org_name: ''
description: 'The highly-customizable Hugo Academic theme powered by Hugo Blox Builder. Easily create your personal academic website.'
twitter: 'BuildLore'
llm:
@@ -35,33 +80,41 @@ marketing:
google: ''
baidu: ''
# Site header
# ==============================================================================
# SITE HEADER
# ==============================================================================
header:
navbar:
enable: true
block: 'navbar'
fixed_to_top: true
show_search: true
show_theme_chooser: true
logo:
text: 'Your Name'
show_mode_toggle: true
show_theme_picker: true
# Site footer
# ==============================================================================
# SITE FOOTER
# ==============================================================================
footer:
copyright:
notice: '© {year} Me. This work is licensed under {license}'
# Use {year} for current year, {name} for branding.organization, {license} for CC license link
notice: '© {year} {name}. This work is licensed under {license}'
license:
enable: true
allow_derivatives: false
share_alike: true
allow_commercial: false
# Localization
# ==============================================================================
# LOCALIZATION
# ==============================================================================
locale:
date_format: 'Jan 2, 2006'
time_format: '3:04 PM'
# Site features
# ==============================================================================
# SITE FEATURES
# ==============================================================================
features:
math:
enable: false
+5 -2
View File
@@ -23,8 +23,11 @@ sections:
education: ''
interests: ''
design:
# Apply a gradient background
css_class: hbx-bg-gradient
# Use the new Gradient Mesh which automatically adapts to the selected theme colors
background:
gradient_mesh:
enable: true
# Avatar customization
avatar:
size: medium # Options: small (150px), medium (200px, default), large (320px), xl (400px), xxl (500px)
Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

@@ -0,0 +1,161 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2f21e4c5",
"metadata": {},
"source": [
"# Ship Notebook Stories in Minutes\n",
"Hugo Blox Notebook renderer turns your `.ipynb` experiments into beautiful long-form posts.\n",
"Use this sample to see how markdown, code, and outputs flow together."
]
},
{
"cell_type": "markdown",
"id": "3078072c",
"metadata": {},
"source": [
"1. Drop notebooks inside `assets/notebooks/` (or import them as page resources).\n",
"2. Reference them with `{{</* notebook src=\"your.ipynb\" */>}}`.\n",
"3. Control code, outputs, metadata badges, and download links via shortcode params."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "7b29b28a",
"metadata": {
"tags": [
"demo",
"quickstart"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Collecting data...\n",
"Training notebook-ready block...\n",
"Done!\n"
]
},
{
"data": {
"text/plain": [
"0.982"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import math\n",
"accuracy = 0.982\n",
"print(\"Collecting data...\")\n",
"print(\"Training notebook-ready block...\")\n",
"print(\"Done!\")\n",
"accuracy"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "ceecfe89",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div style='font-family:Inter,ui-sans-serif;'><strong>Launch Readiness:</strong> <span style='color:#22c55e;'>98.2% confidence</span><br><em>Notebook blocks are theme-aware and dark-mode friendly.</em></div>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from IPython.display import HTML\n",
"HTML(\"<div style='font-family:Inter,ui-sans-serif;'><strong>Launch Readiness:</strong> <span style='color:#22c55e;'>98.2% confidence</span><br><em>Notebook blocks are theme-aware and dark-mode friendly.</em></div>\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "664e49ca",
"metadata": {
"tags": [
"metrics"
]
},
"outputs": [
{
"data": {
"application/json": {
"metrics": {
"engagement_rate": 0.73,
"read_time_minutes": 4.6,
"subscribers": 1280
}
},
"text/plain": [
"{'metrics': {'engagement_rate': 0.73, 'read_time_minutes': 4.6, 'subscribers': 1280}}"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"metrics = {\n",
" 'metrics': {\n",
" 'engagement_rate': 0.73,\n",
" 'read_time_minutes': 4.6,\n",
" 'subscribers': 1280\n",
" }\n",
"}\n",
"metrics"
]
},
{
"cell_type": "markdown",
"id": "0f761ba1",
"metadata": {},
"source": [
"> Tip: Pair this block with Call-to-Action cards or the Embed shortcode to link to GitHub repos, datasets, or ARXIV preprints."
]
}
],
"metadata": {
"authors": [
{
"name": "HugoBlox Studio"
}
],
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,66 @@
---
title: ⚡️ Turn Jupyter Notebooks into Blog Posts
summary: Publish your data science and research directly from Jupyter Notebooks. No screenshots required.
date: 2024-07-15
authors:
- admin
tags:
- Hugo Blox
- Jupyter
- Open Science
- Tutorials
image:
caption: "Image credit: [HugoBlox](https://hugoblox.com)"
focal_point: Center
placement: 1
content_meta:
trending: true
---
As a researcher or data scientist, your work often lives in Jupyter Notebooks. But sharing those insights effectively usually means taking screenshots, messy copy-pasting, or exporting to PDF.
Hugo Blox changes that. With the `{{</* notebook */>}}` shortcode, you can render your actual `.ipynb` files directly as beautiful, interactive blog posts or project pages. Keep your code, outputs, and narrative in one place.
{{< toc mobile_only=true is_open=true >}}
## Why publish notebooks?
> [!TIP]
> **Reproducible Research**: By publishing the actual notebook, you allow others to download and run your code, verifying your results and building upon your work.
- **No more screenshots** – Render crisp code and vector plots directly from your source.
- **Theme consistent** – Notebooks automatically adapt to your site's theme (including dark mode).
- **Flexible sourcing** – Display notebooks from your `assets/` folder, page bundles, or even directly from a remote GitHub URL.
- **Interactive** – Users can copy code blocks or download the full notebook to run locally.
## Example: Data Science Workflow
Below is a live example of a notebook rendered right here in this post. Notice how the markdown, code, and outputs (text, HTML, and JSON) are all preserved and styled.
{{< notebook
src="hugoblox-onboarding.ipynb"
title="Launch Readiness Analysis"
show_metadata=true
line_numbers=true
dense=false
download_label="Download notebook"
show_outputs=true
>}}
## How to add a notebook
1. **Save your notebook.** Place your `.ipynb` file in `assets/notebooks/` (for global access) or inside a page bundle (like `content/blog/my-post/analysis.ipynb`).
2. **Add the shortcode.** In any Markdown page, simply use:
`{{</* notebook src="analysis.ipynb" */>}}`
3. **Customize.** You can hide code cells for non-technical audiences (`show_code=false`) or just show the output (`show_outputs=true`).
> [!IMPORTANT]
> Hugo Blox respects your privacy. Notebook rendering happens statically at build time—no third-party services required.
## Next steps
- **Try it out:** Drop one of your existing notebooks into this site and see how it looks.
- **Link your papers:** Use the Embed shortcode to link your notebook to your latest arXiv preprint or GitHub repository.
- **Get help:** Join the community on [Discord](https://discord.gg/z8wNYzb) or check the [documentation](https://docs.hugoblox.com).
Happy researching! 🚀
+3 -2
View File
@@ -3,7 +3,8 @@
# Hugo Documentation: https://gohugo.io/getting-started/configuration/#all-configuration-settings
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
title: "Creator's Blog (Hugo Theme)" # Website name
# Website name (set in params.yaml under hugoblox.branding.name)
title: ''
baseURL: 'https://example.com/' # Website URL
############################
@@ -51,7 +52,7 @@ pagination:
enableEmoji: true
enableRobotsTXT: true
footnotereturnlinkcontents: <sup>^</sup>
ignoreFiles: [\.ipynb$, .ipynb_checkpoints$, \.Rmd$, \.Rmarkdown$, _cache$]
ignoreFiles: [".ipynb_checkpoints$", "\\.Rmd$", "\\.Rmarkdown$", "_cache$"]
disableAliases: true
outputs:
home: [HTML, RSS, headers, redirects, backlinks]
+46 -14
View File
@@ -3,19 +3,42 @@
# Documentation: https://docs.hugoblox.com/
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
# Appearance
appearance:
mode: system
theme:
light: "default"
dark: "default"
# ==============================================================================
# HUGO BLOX CONFIGURATION
# ==============================================================================
hugoblox:
# ────────────────────────────────────────────────────────────────────────────
# BRANDING
# Your site's identity - used in navbar, footer, SEO, and copyright
# ────────────────────────────────────────────────────────────────────────────
branding:
name: "My Blog"
# SEO
# ────────────────────────────────────────────────────────────────────────────
# THEME
# Controls colors and appearance
# ────────────────────────────────────────────────────────────────────────────
theme:
mode: system
pack: "default"
# ────────────────────────────────────────────────────────────────────────────
# TYPOGRAPHY & LAYOUT
# ────────────────────────────────────────────────────────────────────────────
typography:
font: "sans"
size: "md"
layout:
radius: "md"
spacing: "comfortable"
# ==============================================================================
# SEO & MARKETING
# ==============================================================================
marketing:
seo:
site_type: Person
local_business_type: ''
org_name: ''
description: 'A highly-customizable Hugo blog theme powered by Hugo Blox Builder.'
twitter: 'BuildLore'
analytics:
@@ -28,29 +51,38 @@ marketing:
google: ''
baidu: ''
# Site header
# ==============================================================================
# SITE HEADER
# ==============================================================================
header:
navbar:
enable: true
show_search: true
show_theme_chooser: true
show_mode_toggle: true
# show_theme_picker: true
# Site footer
# ==============================================================================
# SITE FOOTER
# ==============================================================================
footer:
copyright:
notice: '© {year} Me. This work is licensed under {license}'
notice: '© {year} {name}. This work is licensed under {license}'
license:
enable: true
allow_derivatives: false
share_alike: true
allow_commercial: false
# Localization
# ==============================================================================
# LOCALIZATION
# ==============================================================================
locale:
date_format: 'Jan 2, 2006'
time_format: '3:04 PM'
# Site features
# ==============================================================================
# SITE FEATURES
# ==============================================================================
features:
math:
enable: false
@@ -3,7 +3,8 @@
# Hugo Documentation: https://gohugo.io/getting-started/configuration/#all-configuration-settings
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
title: "Hugo Docs" # Website name
# Website name (set in params.yaml under hugoblox.branding.name)
title: ''
baseURL: 'https://example.com/' # Website URL
############################
@@ -60,7 +61,7 @@ pagination:
enableEmoji: true
enableRobotsTXT: true
footnotereturnlinkcontents: <sup>^</sup>
ignoreFiles: [\.ipynb$, .ipynb_checkpoints$, \.Rmd$, \.Rmarkdown$, _cache$]
ignoreFiles: [".ipynb_checkpoints$", "\\.Rmd$", "\\.Rmarkdown$", "_cache$"]
enableInlineShortcodes: true
disableAliases: true
outputs:
@@ -3,19 +3,43 @@
# Documentation: https://docs.hugoblox.com/
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
# Appearance
appearance:
mode: light
theme:
light: "default"
dark: "default"
# ==============================================================================
# HUGO BLOX CONFIGURATION
# ==============================================================================
hugoblox:
# ────────────────────────────────────────────────────────────────────────────
# BRANDING
# Your site's identity - used in navbar, footer, SEO, and copyright
# ────────────────────────────────────────────────────────────────────────────
branding:
name: "My Docs"
# Logo auto-detected from assets/media/logo.{svg,png}
# SEO
# ────────────────────────────────────────────────────────────────────────────
# THEME
# Controls colors and appearance
# ────────────────────────────────────────────────────────────────────────────
theme:
mode: light
pack: "default"
# ────────────────────────────────────────────────────────────────────────────
# TYPOGRAPHY & LAYOUT
# ────────────────────────────────────────────────────────────────────────────
typography:
font: "sans"
size: "md"
layout:
radius: "sm"
spacing: "comfortable"
# ==============================================================================
# SEO & MARKETING
# ==============================================================================
marketing:
seo:
site_type: Project
local_business_type: ''
org_name: ''
description: 'A highly-customizable Hugo documentation theme powered by Hugo Blox website builder.'
twitter: 'BuildLore'
analytics:
@@ -30,40 +54,45 @@ marketing:
google: ''
baidu: ''
# Site header
# ==============================================================================
# SITE HEADER
# ==============================================================================
header:
navbar:
enable: true
blox: "navbar"
align: "center"
fixed_to_top: true
logo:
text: ""
# Logo filename in `assets/media/` folder
filename: "logo.svg"
button:
enable: true
text: "Get Started"
url: "https://hugoblox.com/templates/details/docs/"
show_search: true
show_theme_chooser: true
show_mode_toggle: true
# show_theme_picker: true
# Site footer
# ==============================================================================
# SITE FOOTER
# ==============================================================================
footer:
copyright:
notice: '© {year} My Company. This work is licensed under {license}'
notice: '© {year} {name}. This work is licensed under {license}'
license:
enable: true
allow_derivatives: false
share_alike: true
allow_commercial: false
# Localization
# ==============================================================================
# LOCALIZATION
# ==============================================================================
locale:
date_format: 'Jan 2, 2006'
time_format: '3:04 PM'
# Site features
# ==============================================================================
# SITE FEATURES
# ==============================================================================
features:
math:
enable: false
@@ -3,7 +3,8 @@
# Hugo Documentation: https://gohugo.io/getting-started/configuration/#all-configuration-settings
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
title: Creator's Landing Page | Hugo Theme # Website name
# Website name (set in params.yaml under hugoblox.branding.name)
title: ''
baseURL: 'https://example.com/' # Website URL
############################
@@ -57,7 +58,7 @@ pagination:
enableEmoji: true
enableRobotsTXT: true
footnotereturnlinkcontents: <sup>^</sup>
ignoreFiles: [\.ipynb$, .ipynb_checkpoints$, \.Rmd$, \.Rmarkdown$, _cache$]
ignoreFiles: [".ipynb_checkpoints$", "\\.Rmd$", "\\.Rmarkdown$", "_cache$"]
disableAliases: true
outputs:
home: [HTML, RSS, headers, redirects]
@@ -3,19 +3,43 @@
# Documentation: https://docs.hugoblox.com/
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
# Appearance
appearance:
mode: light
theme:
light: "default"
dark: "default"
# ==============================================================================
# HUGO BLOX CONFIGURATION
# ==============================================================================
hugoblox:
# ────────────────────────────────────────────────────────────────────────────
# BRANDING
# Your site's identity - used in navbar, footer, SEO, and copyright
# ────────────────────────────────────────────────────────────────────────────
branding:
name: "My Company"
# Logo auto-detected from assets/media/logo.{svg,png}
# SEO
# ────────────────────────────────────────────────────────────────────────────
# THEME
# Controls colors and appearance
# ────────────────────────────────────────────────────────────────────────────
theme:
mode: light
pack: "default"
# ────────────────────────────────────────────────────────────────────────────
# TYPOGRAPHY & LAYOUT
# ────────────────────────────────────────────────────────────────────────────
typography:
font: "sans"
size: "md"
layout:
radius: "md"
spacing: "comfortable"
# ==============================================================================
# SEO & MARKETING
# ==============================================================================
marketing:
seo:
site_type: Person
local_business_type: ''
org_name: ''
description: 'A highly-customizable Hugo landing page theme powered by Hugo Blox website builder.'
twitter: 'BuildLore'
llm:
@@ -35,30 +59,33 @@ marketing:
google: ''
baidu: ''
# Site header
# ==============================================================================
# SITE HEADER
# ==============================================================================
header:
navbar:
enable: true
block: 'navbar'
align: 'center'
fixed_to_top: true
logo:
text: ''
# Logo filename in `assets/media/` folder
filename: 'logo.svg'
button:
enable: true
text: 'Get Started'
url: 'https://hugoblox.com/templates/'
show_search: true
show_theme_chooser: true
show_mode_toggle: true
# show_theme_picker: true
# Site footer
# ==============================================================================
# SITE FOOTER
# ==============================================================================
footer:
copyright:
notice: '© {year} My Company'
notice: '© {year} {name}'
# Localization
# ==============================================================================
# LOCALIZATION
# ==============================================================================
locale:
date_format: 'Jan 2, 2006'
time_format: '3:04 PM'
@@ -3,7 +3,8 @@
# Hugo Documentation: https://gohugo.io/getting-started/configuration/#all-configuration-settings
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
title: Fast & Free Link In Bio Website (Hugo Theme) # Website name
# Website name (set in params.yaml under hugoblox.branding.name)
title: ''
baseURL: 'https://example.com/' # Website URL
############################
@@ -28,7 +29,7 @@ pagination:
enableEmoji: true
enableRobotsTXT: true
footnotereturnlinkcontents: <sup>^</sup>
ignoreFiles: [\.ipynb$, .ipynb_checkpoints$, \.Rmd$, \.Rmarkdown$, _cache$]
ignoreFiles: [".ipynb_checkpoints$", "\\.Rmd$", "\\.Rmarkdown$", "_cache$"]
disableAliases: true
outputs:
home: [HTML, RSS, headers, redirects]
@@ -3,19 +3,42 @@
# Documentation: https://docs.hugoblox.com/
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
# Appearance
appearance:
mode: light
theme:
light: "default"
dark: "default"
# ==============================================================================
# HUGO BLOX CONFIGURATION
# ==============================================================================
hugoblox:
# ────────────────────────────────────────────────────────────────────────────
# BRANDING
# Your site's identity - used in navbar, footer, SEO, and copyright
# ────────────────────────────────────────────────────────────────────────────
branding:
name: "Your Name"
# SEO
# ────────────────────────────────────────────────────────────────────────────
# THEME
# Controls colors and appearance
# ────────────────────────────────────────────────────────────────────────────
theme:
mode: light
pack: "default"
# ────────────────────────────────────────────────────────────────────────────
# TYPOGRAPHY & LAYOUT
# ────────────────────────────────────────────────────────────────────────────
typography:
font: "sans"
size: "md"
layout:
radius: "lg"
spacing: "comfortable"
# ==============================================================================
# SEO & MARKETING
# ==============================================================================
marketing:
seo:
site_type: Person
local_business_type: ''
org_name: ''
description: 'A highly-customizable Hugo link in bio theme powered by Hugo Blox website builder.'
twitter: 'BuildLore'
analytics:
@@ -28,18 +51,24 @@ marketing:
google: ''
baidu: ''
# Site header
# ==============================================================================
# SITE HEADER
# ==============================================================================
header:
navbar:
enable: true
block: floating-theme-toggler
# Site footer
# ==============================================================================
# SITE FOOTER
# ==============================================================================
footer:
copyright:
notice: '© {year} Me'
notice: '© {year} {name}'
# Localization
# ==============================================================================
# LOCALIZATION
# ==============================================================================
locale:
date_format: 'Jan 2, 2006'
time_format: '3:04 PM'
+3 -2
View File
@@ -3,7 +3,8 @@
# Hugo Documentation: https://gohugo.io/getting-started/configuration/#all-configuration-settings
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
title: Résumé Hugo Theme # Website name
# Website name (set in params.yaml under hugoblox.branding.name)
title: ''
baseURL: 'https://example.com/' # Website URL
############################
@@ -28,7 +29,7 @@ pagination:
enableEmoji: true
enableRobotsTXT: true
footnotereturnlinkcontents: <sup>^</sup>
ignoreFiles: [\.ipynb$, .ipynb_checkpoints$, \.Rmd$, \.Rmarkdown$, _cache$]
ignoreFiles: [".ipynb_checkpoints$", "\\.Rmd$", "\\.Rmarkdown$", "_cache$"]
disableAliases: true
outputs:
home: [HTML, RSS, headers, redirects]
+44 -13
View File
@@ -3,19 +3,42 @@
# Documentation: https://docs.hugoblox.com/
# This file is formatted using YAML syntax - learn more at https://learnxinyminutes.com/docs/yaml/
# Appearance
appearance:
mode: system
theme:
light: "default"
dark: "default"
# ==============================================================================
# HUGO BLOX CONFIGURATION
# ==============================================================================
hugoblox:
# ────────────────────────────────────────────────────────────────────────────
# BRANDING
# Your site's identity - used in navbar, footer, SEO, and copyright
# ────────────────────────────────────────────────────────────────────────────
branding:
name: "Your Name"
# SEO
# ────────────────────────────────────────────────────────────────────────────
# THEME
# Controls colors and appearance
# ────────────────────────────────────────────────────────────────────────────
theme:
mode: system
pack: "default"
# ────────────────────────────────────────────────────────────────────────────
# TYPOGRAPHY & LAYOUT
# ────────────────────────────────────────────────────────────────────────────
typography:
font: "sans"
size: "md"
layout:
radius: "md"
spacing: "comfortable"
# ==============================================================================
# SEO & MARKETING
# ==============================================================================
marketing:
seo:
site_type: Person
local_business_type: ''
org_name: ''
description: 'A highly-customizable Hugo résumé theme powered by Hugo Blox Builder.'
twitter: 'BuildLore'
analytics:
@@ -28,28 +51,36 @@ marketing:
google: ''
baidu: ''
# Site header
# ==============================================================================
# SITE HEADER
# ==============================================================================
header:
navbar:
enable: true
block: floating-theme-toggler
# Site footer
# ==============================================================================
# SITE FOOTER
# ==============================================================================
footer:
copyright:
notice: '© {year} Me. This work is licensed under {license}'
notice: '© {year} {name}. This work is licensed under {license}'
license:
enable: true
allow_derivatives: false
share_alike: true
allow_commercial: false
# Localization
# ==============================================================================
# LOCALIZATION
# ==============================================================================
locale:
date_format: 'Jan 2, 2006'
time_format: '3:04 PM'
# Site features
# ==============================================================================
# SITE FEATURES
# ==============================================================================
features:
math:
enable: false