diff --git a/themes/bonsai/.gitignore b/themes/bonsai/.gitignore new file mode 100644 index 0000000..d6ece0c --- /dev/null +++ b/themes/bonsai/.gitignore @@ -0,0 +1,19 @@ +# Hugo build output +public/ +resources/ +.hugo_build.lock + +# Hugo modules +node_modules/ + +# OS / editor +.DS_Store +Thumbs.db +.idea/ +.vscode/ +*.swp +*.swo + +# Local env +.env +.env.local diff --git a/themes/bonsai/LICENSE b/themes/bonsai/LICENSE new file mode 100644 index 0000000..57bc88a --- /dev/null +++ b/themes/bonsai/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/themes/bonsai/README.md b/themes/bonsai/README.md new file mode 100644 index 0000000..05f53cd --- /dev/null +++ b/themes/bonsai/README.md @@ -0,0 +1,91 @@ +# Bonsai + +A minimalist Hugo theme for link-in-bio pages, inspired by [Linktree](https://linktr.ee) and the Japanese art of [bonsai](https://en.wikipedia.org/wiki/Bonsai) — *small, curated, intentional*. + +> 盆栽 (bonsai): "tray planting" — the art of growing miniature trees through patient, deliberate cultivation. Every branch placed with care. + +Bonsai treats your bio page the same way: a quiet, well-pruned page that surfaces only what matters — your name, who you are, and where people can find you. + +## Features + +- **Single-page bio** — name, avatar, tagline, links, that's it +- **Data-driven links** — define every link in `hugo.toml` (or `data/links.yaml`); no content files needed +- **Light & dark mode** — respects system preference, toggleable +- **Zero JavaScript by default** — pure HTML + CSS; opt-in JS for theme toggle +- **Responsive** — mobile-first, looks right on every screen +- **Japanese-aesthetic defaults** — generous whitespace, calm palette, restrained typography +- **Fast** — < 10 KB CSS, no web fonts required (system stack) +- **Accessible** — semantic HTML, focus states, prefers-reduced-motion + +## Installation + +### As a Hugo Module (recommended) + +```bash +hugo mod init github.com// +hugo mod get github.com/tiennm99/bonsai +``` + +Add to `hugo.toml`: + +```toml +[module] + [[module.imports]] + path = "github.com/tiennm99/bonsai" +``` + +### As a Git submodule + +```bash +git submodule add https://github.com/tiennm99/bonsai.git themes/bonsai +``` + +Set in `hugo.toml`: + +```toml +theme = "bonsai" +``` + +## Configuration + +Minimal `hugo.toml`: + +```toml +baseURL = "https://example.com/" +title = "Your Name" +theme = "bonsai" + +[params] + name = "Your Name" + tagline = "Tending my little corner of the internet" + avatar = "/images/avatar.jpg" + bio = "Short bio. One sentence is plenty." + + [[params.links]] + title = "GitHub" + url = "https://github.com/yourname" + icon = "github" + + [[params.links]] + title = "Blog" + url = "https://yourblog.com" + icon = "globe" + + [[params.links]] + title = "Email" + url = "mailto:you@example.com" + icon = "mail" +``` + +See `exampleSite/` for a full reference. + +## Development + +```bash +cd exampleSite +hugo server --themesDir ../.. +``` + +## License + +Apache-2.0 © tiennm99 diff --git a/themes/bonsai/archetypes/default.md b/themes/bonsai/archetypes/default.md new file mode 100644 index 0000000..c6f3fce --- /dev/null +++ b/themes/bonsai/archetypes/default.md @@ -0,0 +1,5 @@ ++++ +title = '{{ replace .File.ContentBaseName "-" " " | title }}' +date = {{ .Date }} +draft = true ++++ diff --git a/themes/bonsai/exampleSite/content/_index.md b/themes/bonsai/exampleSite/content/_index.md new file mode 100644 index 0000000..e6dbe72 --- /dev/null +++ b/themes/bonsai/exampleSite/content/_index.md @@ -0,0 +1,5 @@ +--- +title: "Home" +--- + +This file exists so Hugo renders the index page. All bio content comes from `[params]` in `hugo.toml`. diff --git a/themes/bonsai/exampleSite/hugo.toml b/themes/bonsai/exampleSite/hugo.toml new file mode 100644 index 0000000..3af41a4 --- /dev/null +++ b/themes/bonsai/exampleSite/hugo.toml @@ -0,0 +1,32 @@ +baseURL = "https://example.com/" +languageCode = "en-us" +title = "Tien Nguyen" +theme = "bonsai" + +[params] + name = "Tien Nguyen" + tagline = "Software engineer · tending small things with care" + bio = "Building tools, writing code, growing a quiet corner of the internet." + avatar = "/images/avatar.jpg" + themeToggle = false + footerText = "© 2026 · made with bonsai" + + [[params.links]] + title = "GitHub" + url = "https://github.com/tiennm99" + icon = "github" + + [[params.links]] + title = "Personal site" + url = "https://example.com" + icon = "globe" + + [[params.links]] + title = "LinkedIn" + url = "https://linkedin.com/in/tiennm99" + icon = "linkedin" + + [[params.links]] + title = "Email me" + url = "mailto:tiennm99@outlook.com" + icon = "mail" diff --git a/themes/bonsai/images/README.md b/themes/bonsai/images/README.md new file mode 100644 index 0000000..0962ace --- /dev/null +++ b/themes/bonsai/images/README.md @@ -0,0 +1,5 @@ +# Theme images + +Place `screenshot.png` (1500×1000) and `tn.png` (900×600) here for submission to the Hugo themes site. + +See: https://github.com/gohugoio/hugoThemesSiteBuilder#requirements diff --git a/themes/bonsai/layouts/_default/baseof.html b/themes/bonsai/layouts/_default/baseof.html new file mode 100644 index 0000000..856a329 --- /dev/null +++ b/themes/bonsai/layouts/_default/baseof.html @@ -0,0 +1,12 @@ + + + + {{- partial "head.html" . -}} + + +
+ {{- block "main" . }}{{- end }} +
+ {{- partial "footer.html" . -}} + + diff --git a/themes/bonsai/layouts/index.html b/themes/bonsai/layouts/index.html new file mode 100644 index 0000000..94d7d3d --- /dev/null +++ b/themes/bonsai/layouts/index.html @@ -0,0 +1,3 @@ +{{ define "main" }} + {{- partial "bio-card.html" . -}} +{{ end }} diff --git a/themes/bonsai/layouts/partials/bio-card.html b/themes/bonsai/layouts/partials/bio-card.html new file mode 100644 index 0000000..85b598b --- /dev/null +++ b/themes/bonsai/layouts/partials/bio-card.html @@ -0,0 +1,29 @@ +{{- $name := site.Params.name | default site.Title -}} +{{- $tagline := site.Params.tagline -}} +{{- $bio := site.Params.bio -}} +{{- $avatar := site.Params.avatar -}} +{{- $links := site.Params.links -}} + +
+ {{- if $avatar }} + {{ $name }} + {{- end }} + +

{{ $name }}

+ + {{- with $tagline }} +

{{ . }}

+ {{- end }} + + {{- with $bio }} +

{{ . | markdownify }}

+ {{- end }} + + {{- if $links }} + + {{- end }} +
diff --git a/themes/bonsai/layouts/partials/footer.html b/themes/bonsai/layouts/partials/footer.html new file mode 100644 index 0000000..c6b761b --- /dev/null +++ b/themes/bonsai/layouts/partials/footer.html @@ -0,0 +1,9 @@ +{{- if site.Params.footer | default true -}} +
+ {{- with site.Params.footerText -}} +

{{ . | safeHTML }}

+ {{- else -}} +

© {{ now.Year }} {{ site.Params.name | default site.Title }}

+ {{- end -}} +
+{{- end -}} diff --git a/themes/bonsai/layouts/partials/head.html b/themes/bonsai/layouts/partials/head.html new file mode 100644 index 0000000..80d9f4c --- /dev/null +++ b/themes/bonsai/layouts/partials/head.html @@ -0,0 +1,27 @@ +{{- $name := site.Params.name | default site.Title -}} +{{- $tagline := site.Params.tagline | default "" -}} +{{- $description := site.Params.bio | default $tagline | default site.Params.description -}} +{{- $avatar := site.Params.avatar -}} + + + +{{ $name }}{{ with $tagline }} — {{ . }}{{ end }} +{{- with $description }} + +{{- end }} + + + +{{- with $description }} + +{{- end }} +{{- with $avatar }} + +{{- end }} + + + + +{{- if site.Params.themeToggle }} + +{{- end }} diff --git a/themes/bonsai/layouts/partials/icon.html b/themes/bonsai/layouts/partials/icon.html new file mode 100644 index 0000000..621c3da --- /dev/null +++ b/themes/bonsai/layouts/partials/icon.html @@ -0,0 +1,21 @@ +{{- $name := . -}} +{{- /* Minimal inline SVG icon set. Stroke uses currentColor so it picks up text color. */ -}} +{{- if eq $name "github" -}} + +{{- else if eq $name "globe" -}} + +{{- else if eq $name "mail" -}} + +{{- else if eq $name "twitter" -}} + +{{- else if eq $name "linkedin" -}} + +{{- else if eq $name "youtube" -}} + +{{- else if eq $name "instagram" -}} + +{{- else if eq $name "rss" -}} + +{{- else -}} + +{{- end -}} diff --git a/themes/bonsai/layouts/partials/link-button.html b/themes/bonsai/layouts/partials/link-button.html new file mode 100644 index 0000000..f553508 --- /dev/null +++ b/themes/bonsai/layouts/partials/link-button.html @@ -0,0 +1,12 @@ +{{- $title := .title -}} +{{- $url := .url -}} +{{- $icon := .icon -}} +{{- $external := strings.HasPrefix $url "http" -}} + + {{- with $icon }} + + {{- end }} + {{ $title }} + diff --git a/themes/bonsai/static/css/bonsai.css b/themes/bonsai/static/css/bonsai.css new file mode 100644 index 0000000..5ad81d8 --- /dev/null +++ b/themes/bonsai/static/css/bonsai.css @@ -0,0 +1,162 @@ +/* Bonsai — minimalist link-in-bio Hugo theme + * Aesthetic: Japanese zen — generous whitespace, calm palette, restrained type. */ + +:root { + --bonsai-bg: #f4efe6; /* washi paper */ + --bonsai-surface: #ffffff; + --bonsai-text: #2b2b2b; /* sumi ink */ + --bonsai-muted: #6b6b6b; + --bonsai-accent: #8b3a2b; /* shu-iro / vermilion seal */ + --bonsai-border: #d9d0bf; + --bonsai-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 8px 24px rgba(0, 0, 0, .04); + + --bonsai-radius: 14px; + --bonsai-gap: clamp(.75rem, 1.6vw, 1rem); + --bonsai-pad: clamp(1.25rem, 4vw, 2rem); + --bonsai-max: 32rem; + + --bonsai-font: ui-sans-serif, system-ui, -apple-system, "Hiragino Sans", "Yu Gothic UI", "Noto Sans JP", "Segoe UI", Roboto, sans-serif; + --bonsai-font-display: ui-serif, Georgia, "Hiragino Mincho ProN", "Yu Mincho", "Noto Serif JP", "Times New Roman", serif; +} + +@media (prefers-color-scheme: dark) { + :root { + --bonsai-bg: #1a1817; + --bonsai-surface: #232020; + --bonsai-text: #ece6d9; + --bonsai-muted: #a09a8d; + --bonsai-accent: #d6856e; + --bonsai-border: #3a3633; + --bonsai-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25); + } +} + +[data-theme="light"] { + --bonsai-bg: #f4efe6; + --bonsai-surface: #ffffff; + --bonsai-text: #2b2b2b; + --bonsai-muted: #6b6b6b; + --bonsai-accent: #8b3a2b; + --bonsai-border: #d9d0bf; +} +[data-theme="dark"] { + --bonsai-bg: #1a1817; + --bonsai-surface: #232020; + --bonsai-text: #ece6d9; + --bonsai-muted: #a09a8d; + --bonsai-accent: #d6856e; + --bonsai-border: #3a3633; +} + +* { box-sizing: border-box; } + +html { -webkit-text-size-adjust: 100%; } + +body { + margin: 0; + min-height: 100dvh; + background: var(--bonsai-bg); + color: var(--bonsai-text); + font-family: var(--bonsai-font); + font-size: 16px; + line-height: 1.55; + -webkit-font-smoothing: antialiased; + display: flex; + flex-direction: column; +} + +.bonsai { + flex: 1; + width: 100%; + max-width: var(--bonsai-max); + margin: 0 auto; + padding: clamp(2rem, 6vh, 4rem) var(--bonsai-pad); +} + +.bio { text-align: center; } + +.bio__avatar { + width: 112px; + height: 112px; + border-radius: 50%; + object-fit: cover; + border: 1px solid var(--bonsai-border); + box-shadow: var(--bonsai-shadow); + margin-bottom: 1.25rem; +} + +.bio__name { + font-family: var(--bonsai-font-display); + font-weight: 600; + font-size: clamp(1.5rem, 3.5vw, 1.875rem); + margin: 0 0 .25rem; + letter-spacing: -.01em; +} + +.bio__tagline { + color: var(--bonsai-muted); + margin: 0 0 1rem; + font-size: .95rem; +} + +.bio__text { + margin: 0 auto 1.75rem; + max-width: 28rem; + color: var(--bonsai-text); + font-size: .98rem; +} + +.bio__links { + display: flex; + flex-direction: column; + gap: var(--bonsai-gap); + margin-top: 1.5rem; +} + +.link { + display: flex; + align-items: center; + justify-content: center; + gap: .625rem; + padding: .9rem 1.25rem; + background: var(--bonsai-surface); + color: var(--bonsai-text); + border: 1px solid var(--bonsai-border); + border-radius: var(--bonsai-radius); + text-decoration: none; + font-weight: 500; + font-size: .95rem; + box-shadow: var(--bonsai-shadow); + transition: transform .15s ease, border-color .15s ease, background .15s ease; +} + +.link:hover { + transform: translateY(-1px); + border-color: var(--bonsai-accent); +} + +.link:focus-visible { + outline: 2px solid var(--bonsai-accent); + outline-offset: 2px; +} + +.link:active { transform: translateY(0); } + +.link__icon { + display: inline-flex; + align-items: center; + color: var(--bonsai-accent); +} + +.link__title { line-height: 1; } + +.bonsai-footer { + text-align: center; + padding: 1.5rem var(--bonsai-pad) 2rem; + color: var(--bonsai-muted); + font-size: .8rem; +} + +@media (prefers-reduced-motion: reduce) { + .link, .link:hover { transition: none; transform: none; } +} diff --git a/themes/bonsai/static/js/theme-toggle.js b/themes/bonsai/static/js/theme-toggle.js new file mode 100644 index 0000000..48c28d3 --- /dev/null +++ b/themes/bonsai/static/js/theme-toggle.js @@ -0,0 +1,17 @@ +// Optional: enable via params.themeToggle = true in hugo.toml +// Persists user preference and overrides system theme. +(function () { + const KEY = 'bonsai-theme'; + const root = document.documentElement; + const saved = localStorage.getItem(KEY); + if (saved === 'light' || saved === 'dark') root.dataset.theme = saved; + + const btn = document.querySelector('[data-bonsai-theme-toggle]'); + if (!btn) return; + + btn.addEventListener('click', () => { + const current = root.dataset.theme === 'dark' ? 'light' : 'dark'; + root.dataset.theme = current; + localStorage.setItem(KEY, current); + }); +})(); diff --git a/themes/bonsai/theme.toml b/themes/bonsai/theme.toml new file mode 100644 index 0000000..fdb402e --- /dev/null +++ b/themes/bonsai/theme.toml @@ -0,0 +1,15 @@ +name = "Bonsai" +license = "Apache-2.0" +licenselink = "https://github.com/tiennm99/bonsai/blob/main/LICENSE" +description = "A minimalist Hugo theme for link-in-bio pages, inspired by Linktree and Japanese bonsai aesthetics — small, curated, intentional." +homepage = "https://github.com/tiennm99/bonsai" +demosite = "https://github.com/tiennm99/bonsai" +tags = ["bio", "linktree", "minimal", "japanese", "personal", "landing"] +features = ["responsive", "dark mode", "fast", "no-js optional", "data-driven links"] +min_version = "0.128.0" + +[author] + name = "tiennm99" + homepage = "https://github.com/tiennm99" + +# If porting/adapting from another theme, list it under [original]