Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eff11d99d8 | ||
|
|
7487aa2c36 | ||
|
|
921390c351 | ||
|
|
7468f3afba | ||
|
|
ce08594810 | ||
|
|
6997564aaa | ||
|
|
50469b0747 | ||
|
|
30da421d7d | ||
|
|
179d456eff | ||
|
|
c163f13eaa | ||
|
|
37ed198b2a | ||
|
|
a67bdc5790 | ||
|
|
f2b6737960 | ||
|
|
3c61e89adb | ||
|
|
abdb0ba3bd | ||
|
|
a968b9dafc | ||
|
|
86cdde9cf4 |
No files matched your search
@@ -20,7 +20,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- id: pages
|
- id: pages
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
[submodule "themes/bonsai"]
|
|
||||||
path = themes/bonsai
|
|
||||||
url = https://github.com/tiennm99/bonsai.git
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# iammiti99
|
# iammiti99
|
||||||
|
|
||||||
Personal link-in-bio page — built with [Hugo](https://gohugo.io) and the
|
Personal link-in-bio page — built with [Hugo](https://gohugo.io) and the
|
||||||
[bonsai](https://github.com/tiennm99/bonsai) theme. Self-hosted Linktree
|
[bonsai](themes/bonsai) theme. Self-hosted Linktree
|
||||||
alternative for my own use.
|
alternative for my own use.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
@@ -14,7 +14,6 @@ Actions builds with Hugo + bonsai → output deploys to GitHub Pages.
|
|||||||
Requires Hugo Extended ≥ 0.128.
|
Requires Hugo Extended ≥ 0.128.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git submodule update --init --recursive
|
|
||||||
hugo server # http://localhost:1313
|
hugo server # http://localhost:1313
|
||||||
hugo --gc --minify # build to ./public
|
hugo --gc --minify # build to ./public
|
||||||
```
|
```
|
||||||
@@ -35,7 +34,7 @@ domain — Hugo will copy it through to the deployed site.
|
|||||||
hugo.yml # site content — edit params here
|
hugo.yml # site content — edit params here
|
||||||
content/_index.md # placeholder so Hugo renders the index
|
content/_index.md # placeholder so Hugo renders the index
|
||||||
static/images/ # avatar etc.
|
static/images/ # avatar etc.
|
||||||
themes/bonsai/ # bonsai theme (git submodule)
|
themes/bonsai/ # bonsai theme (in-repo, full history)
|
||||||
.github/workflows/deploy.yml
|
.github/workflows/deploy.yml
|
||||||
public/ # build output (gitignored)
|
public/ # build output (gitignored)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
day: monday
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
commit-message:
|
||||||
|
prefix: chore
|
||||||
|
include: scope
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: build-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Hugo Extended
|
||||||
|
uses: peaceiris/actions-hugo@v3
|
||||||
|
with:
|
||||||
|
hugo-version: '0.154.0'
|
||||||
|
extended: true
|
||||||
|
|
||||||
|
- name: Build exampleSite
|
||||||
|
working-directory: exampleSite
|
||||||
|
run: hugo --gc --minify --themesDir ../.. --printPathWarnings
|
||||||
|
|
||||||
|
- name: Upload built site
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: exampleSite-public
|
||||||
|
path: exampleSite/public
|
||||||
|
retention-days: 7
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
name: deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- id: pages
|
||||||
|
uses: actions/configure-pages@v5
|
||||||
|
|
||||||
|
- name: Setup Hugo Extended
|
||||||
|
uses: peaceiris/actions-hugo@v3
|
||||||
|
with:
|
||||||
|
hugo-version: '0.154.0'
|
||||||
|
extended: true
|
||||||
|
|
||||||
|
- name: Build with production baseURL
|
||||||
|
working-directory: exampleSite
|
||||||
|
run: |
|
||||||
|
hugo \
|
||||||
|
--gc \
|
||||||
|
--minify \
|
||||||
|
--themesDir ../.. \
|
||||||
|
--baseURL "${{ steps.pages.outputs.base_url }}/"
|
||||||
|
|
||||||
|
- name: Upload Pages artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: exampleSite/public
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: build
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [SemVer](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- (placeholder for v0.3 — layout variants, i18n support)
|
||||||
|
|
||||||
|
## [0.2.0] — 2026-05-03
|
||||||
|
|
||||||
|
Polish & SEO release. All additions are strictly opt-in; v0.1 sites upgrade with no config edits.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Color theme presets** — four named palettes (`bonsai` default, `sakura`, `sumi`, `koi`); select via new `params.colorTheme`. Each has light + dark variants. Gallery at `/themes/`.
|
||||||
|
- **schema.org Person markup** — JSON-LD `ProfilePage > Person` injected in `<head>`. Built from existing params + optional `params.jobTitle`, `params.location`, `params.email`. Suppressible via `params.schema = false`. `mailto:` and `tel:` links excluded from `sameAs`.
|
||||||
|
- **Theme toggle UI button** — sun/moon button rendered in footer when `params.themeToggle = true`. Sun shown in dark mode, moon in light (button shows target). `aria-pressed` reflects state. Existing localStorage persistence preserved.
|
||||||
|
- **Avatar initials fallback** — when `params.avatar` is unset, theme renders an inline SVG circle with auto-derived initials (first letter of up to 2 words from `params.name`). Override with `params.avatarInitials`. Background overrideable via `params.avatarBg`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `static/css/bonsai.css` restructured — color vars now scoped per `[data-bonsai-theme]`; layout vars (radius/gap/pad/fonts) stay in `:root`.
|
||||||
|
- `layouts/_default/baseof.html` emits `data-bonsai-theme` attribute on `<html>`.
|
||||||
|
- `layouts/partials/bio-card.html` delegates avatar rendering to new `partials/avatar.html`.
|
||||||
|
- `static/js/theme-toggle.js` syncs `aria-pressed` on first paint and on click.
|
||||||
|
- `exampleSite/hugo.toml` switches demo to `colorTheme = "sakura"` to showcase a non-default palette.
|
||||||
|
|
||||||
|
## [0.1.0] — 2026-05-01
|
||||||
|
|
||||||
|
First production-ready release. Live demo at <https://tiennm99.github.io/bonsai/>.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- 35 icons out of the box: 25 brand (Simple Icons CC0) + 10 utility (Lucide ISC), vendored at build time
|
||||||
|
- `data/icons.yaml` manifest mapping public icon names → vendored SVG paths
|
||||||
|
- Data-driven `partials/icon.html` (13 lines) replacing the hand-curated `if/else if` chain
|
||||||
|
- `scripts/sync-icons.sh` — idempotent vendoring script, version-pinned to `simple-icons@13` + `lucide-static@0.460`
|
||||||
|
- Icon gallery page at `/icons/` (rendered from `exampleSite/content/icons/`)
|
||||||
|
- GitHub Actions `build` workflow — `hugo --gc --minify` on every PR and `main` push
|
||||||
|
- GitHub Actions `deploy` workflow — auto-deploys `exampleSite/` to GitHub Pages on `main`
|
||||||
|
- Dependabot config for weekly action updates
|
||||||
|
- `CONTRIBUTING.md` covering dev setup, icon-add workflow, PR guidelines
|
||||||
|
- `NOTICE` file crediting third-party icon sources
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- README rewritten with badges, live-demo link, configuration table, all-parameters reference
|
||||||
|
- `exampleSite/hugo.toml` adds Mastodon and Bluesky example links
|
||||||
|
- `theme.toml` `demosite` field points at the live GitHub Pages URL
|
||||||
|
|
||||||
|
## [0.0.1] — 2026-05-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Initial scaffold: theme structure, baseof/index/partial layouts, washi-paper light + sumi-ink dark CSS, 8 hand-curated inline SVG icons (github, globe, mail, twitter, linkedin, youtube, instagram, rss), exampleSite, Apache-2.0 LICENSE
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Contributing to Bonsai
|
||||||
|
|
||||||
|
Thanks for your interest. Bonsai is intentionally small — every change should make it simpler, faster, or more useful, never bigger for its own sake.
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/tiennm99/bonsai.git
|
||||||
|
cd bonsai/exampleSite
|
||||||
|
hugo server --themesDir ../.. --bind 0.0.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://localhost:1313/`. Edits to `layouts/`, `static/`, `data/`, and `assets/` hot-reload.
|
||||||
|
|
||||||
|
Hugo Extended ≥ 0.128 required. CI pins 0.154.0.
|
||||||
|
|
||||||
|
## Adding an icon
|
||||||
|
|
||||||
|
The icon system is data-driven — adding one means three small edits.
|
||||||
|
|
||||||
|
1. **Vendor the SVG.** Edit `scripts/sync-icons.sh`:
|
||||||
|
- For brand/social icons → add the slug to the `BRAND_SLUGS` list (sourced from [Simple Icons](https://simpleicons.org)).
|
||||||
|
- For UI/utility icons → add the slug to the `UI_SLUGS` list (sourced from [Lucide](https://lucide.dev)).
|
||||||
|
2. **Run the script** to fetch and normalise:
|
||||||
|
```bash
|
||||||
|
./scripts/sync-icons.sh
|
||||||
|
```
|
||||||
|
3. **Register the public name** in `data/icons.yaml`:
|
||||||
|
```yaml
|
||||||
|
newicon: { family: brand, slug: newicon }
|
||||||
|
```
|
||||||
|
4. Rebuild and verify the icon renders in the gallery: `cd exampleSite && hugo server --themesDir ../..` → `http://localhost:1313/icons/`
|
||||||
|
5. Add the new name to the appropriate table in `README.md`.
|
||||||
|
|
||||||
|
## Pull requests
|
||||||
|
|
||||||
|
- Branch from `main`. Keep PRs focused — one concern per PR.
|
||||||
|
- Conventional commit prefixes: `feat:`, `fix:`, `docs:`, `chore:`, `ci:`, `refactor:`.
|
||||||
|
- For visual changes, attach a before/after screenshot (the live demo URL helps reviewers).
|
||||||
|
- CI must be green (`build` workflow runs `hugo --gc --minify` against `exampleSite`).
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- HTML & CSS: 2-space indent. Keep `partials/` files under 30 lines where possible.
|
||||||
|
- Hugo templates: prefer `{{- ... -}}` to suppress whitespace; use `partials` for reuse.
|
||||||
|
- CSS: BEM-ish naming (`.bio__name`, `.link__icon`); CSS custom properties for theming.
|
||||||
|
- No build steps for end users — vendored assets only. If you reach for `npm install`, reconsider.
|
||||||
|
|
||||||
|
## Reporting issues
|
||||||
|
|
||||||
|
Include: Hugo version (`hugo version`), theme version/commit, minimal reproduction (a snippet of `hugo.toml` is usually enough), expected vs actual.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
YAGNI · KISS · DRY. If a feature would be useful for *some* sites, it doesn't belong here unless it's useful for *most* link-in-bio sites. The strength of Bonsai is what it leaves out.
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Bonsai Hugo Theme — Third-Party Notices
|
||||||
|
========================================
|
||||||
|
|
||||||
|
This theme vendors SVG icons from two open-source projects:
|
||||||
|
|
||||||
|
1. Simple Icons (brand icons)
|
||||||
|
Source: https://simpleicons.org / https://github.com/simple-icons/simple-icons
|
||||||
|
Version: 13.0.0
|
||||||
|
License: CC0 1.0 Universal (Public Domain Dedication)
|
||||||
|
https://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
Files: assets/icons/brand/*.svg
|
||||||
|
|
||||||
|
2. Lucide (UI icons)
|
||||||
|
Source: https://lucide.dev / https://github.com/lucide-icons/lucide
|
||||||
|
Version: 0.460.0 (lucide-static)
|
||||||
|
License: ISC License
|
||||||
|
Copyright (c) 2020, Lucide Contributors
|
||||||
|
Permission to use, copy, modify, and/or distribute this software
|
||||||
|
for any purpose with or without fee is hereby granted, provided
|
||||||
|
that the above copyright notice and this permission notice appear
|
||||||
|
in all copies.
|
||||||
|
Files: assets/icons/ui/*.svg
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
# Bonsai
|
||||||
|
|
||||||
|
[](https://github.com/tiennm99/bonsai/actions/workflows/build.yml)
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://gohugo.io)
|
||||||
|
|
||||||
|
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*.
|
||||||
|
|
||||||
|
**→ [Live demo](https://tiennm99.github.io/bonsai/)** · **[Icon gallery](https://tiennm99.github.io/bonsai/icons/)**
|
||||||
|
|
||||||
|
> 盆栽 (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. Nothing else.
|
||||||
|
- **Data-driven links** — defined in `[[params.links]]`; no content files required.
|
||||||
|
- **35 icons out of the box** — 25 brand (GitHub, Mastodon, Bluesky, X, Threads, LinkedIn, Instagram…) + 10 utility (mail, globe, rss…). Vendored from [Simple Icons](https://simpleicons.org) and [Lucide](https://lucide.dev).
|
||||||
|
- **Light & dark mode** — respects `prefers-color-scheme`; optional toggle.
|
||||||
|
- **Zero JavaScript by default** — pure HTML + CSS; opt-in JS for theme toggle only.
|
||||||
|
- **Fast** — < 4 KB CSS, < 4 KB HTML, no web fonts (system stack), no runtime fetches.
|
||||||
|
- **Accessible** — semantic HTML, focus-visible outlines, `prefers-reduced-motion`.
|
||||||
|
- **Responsive** — mobile-first, looks right at every viewport.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### As a Git submodule (simplest)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule add https://github.com/tiennm99/bonsai.git themes/bonsai
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `hugo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
theme = "bonsai"
|
||||||
|
```
|
||||||
|
|
||||||
|
### As a Hugo Module
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hugo mod init github.com/<you>/<your-site>
|
||||||
|
hugo mod get github.com/tiennm99/bonsai
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `hugo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[module]
|
||||||
|
[[module.imports]]
|
||||||
|
path = "github.com/tiennm99/bonsai"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Minimal `hugo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
baseURL = "https://example.com/"
|
||||||
|
title = "Your Name"
|
||||||
|
theme = "bonsai"
|
||||||
|
|
||||||
|
# Single-page bio — disable everything Hugo doesn't need.
|
||||||
|
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "404"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
name = "Your Name"
|
||||||
|
tagline = "Tending my little corner of the internet"
|
||||||
|
bio = "Short bio. One sentence is plenty."
|
||||||
|
avatar = "/images/avatar.jpg"
|
||||||
|
|
||||||
|
[[params.links]]
|
||||||
|
title = "GitHub"
|
||||||
|
url = "https://github.com/yourname"
|
||||||
|
icon = "github"
|
||||||
|
|
||||||
|
[[params.links]]
|
||||||
|
title = "Email"
|
||||||
|
url = "mailto:you@example.com"
|
||||||
|
icon = "mail"
|
||||||
|
```
|
||||||
|
|
||||||
|
### All parameters
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `name` | string | site `title` | Display name shown as `<h1>`. |
|
||||||
|
| `tagline` | string | — | One-liner under the name. |
|
||||||
|
| `bio` | string (markdown) | — | Short bio paragraph. Markdown supported. |
|
||||||
|
| `avatar` | string (URL) | — | Avatar image path. If unset, theme renders an SVG circle with auto-derived initials. |
|
||||||
|
| `avatarInitials` | string | first letters of `name` | Override the initials when no `avatar` is set. |
|
||||||
|
| `avatarBg` | string (CSS color) | `var(--bonsai-accent)` | Background color of the initials circle. |
|
||||||
|
| `favicon` | string (URL) | `/favicon.ico` | Favicon path. |
|
||||||
|
| `colorTheme` | string | `bonsai` | Palette: `bonsai`, `sakura`, `sumi`, or `koi`. See [Color themes](#color-themes). |
|
||||||
|
| `themeToggle` | bool | `false` | Render a sun/moon button in the footer + load the toggle script. |
|
||||||
|
| `schema` | bool | `true` | Emit schema.org `Person` JSON-LD in `<head>`. Set `false` if you provide your own. |
|
||||||
|
| `jobTitle` | string | — | Optional `Person.jobTitle` field for JSON-LD. |
|
||||||
|
| `location` | string | — | Optional `Person.address` field for JSON-LD. |
|
||||||
|
| `email` | string | — | Optional `Person.email` field for JSON-LD. |
|
||||||
|
| `footer` | bool | `true` | Show the footer. |
|
||||||
|
| `footerText` | string (HTML) | `© {year} {name}` | Override footer text. HTML allowed. |
|
||||||
|
| `links` | array | — | Bio links. See below. |
|
||||||
|
|
||||||
|
**Each `[[params.links]]` entry:**
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `title` | string | yes | Link label. |
|
||||||
|
| `url` | string | yes | Link target. `mailto:` and `tel:` are rendered without `target=_blank`. |
|
||||||
|
| `icon` | string | no | Icon name from the available set (see below). Unknown names render a generic external-link glyph. |
|
||||||
|
|
||||||
|
## Color themes
|
||||||
|
|
||||||
|
Four built-in palettes, each with light + dark variants. Set `colorTheme` in `[params]`:
|
||||||
|
|
||||||
|
| Name | Vibe | Accent |
|
||||||
|
|------|------|--------|
|
||||||
|
| `bonsai` *(default)* | washi paper + vermilion seal | `#8b3a2b` |
|
||||||
|
| `sakura` | cherry blossom pink | `#d4456a` |
|
||||||
|
| `sumi` | monochrome ink | `#1a1a1a` |
|
||||||
|
| `koi` | orange + cream | `#c8521e` |
|
||||||
|
|
||||||
|
Live preview: **[tiennm99.github.io/bonsai/themes/](https://tiennm99.github.io/bonsai/themes/)**.
|
||||||
|
|
||||||
|
## Available Icons
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Brand / Social (25)</summary>
|
||||||
|
|
||||||
|
| Name | Source |
|
||||||
|
|------|--------|
|
||||||
|
| `github` | Simple Icons |
|
||||||
|
| `gitlab` | Simple Icons |
|
||||||
|
| `mastodon` | Simple Icons |
|
||||||
|
| `bsky` | Simple Icons |
|
||||||
|
| `x` | Simple Icons |
|
||||||
|
| `threads` | Simple Icons |
|
||||||
|
| `linkedin` | Simple Icons |
|
||||||
|
| `instagram` | Simple Icons |
|
||||||
|
| `facebook` | Simple Icons |
|
||||||
|
| `tiktok` | Simple Icons |
|
||||||
|
| `youtube` | Simple Icons |
|
||||||
|
| `twitch` | Simple Icons |
|
||||||
|
| `discord` | Simple Icons |
|
||||||
|
| `telegram` | Simple Icons |
|
||||||
|
| `signal` | Simple Icons |
|
||||||
|
| `whatsapp` | Simple Icons |
|
||||||
|
| `reddit` | Simple Icons |
|
||||||
|
| `medium` | Simple Icons |
|
||||||
|
| `devto` | Simple Icons |
|
||||||
|
| `substack` | Simple Icons |
|
||||||
|
| `hashnode` | Simple Icons |
|
||||||
|
| `kofi` | Simple Icons |
|
||||||
|
| `patreon` | Simple Icons |
|
||||||
|
| `buymeacoffee` | Simple Icons |
|
||||||
|
| `paypal` | Simple Icons |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>UI / Utility (10)</summary>
|
||||||
|
|
||||||
|
| Name | Source |
|
||||||
|
|------|--------|
|
||||||
|
| `mail` | Lucide |
|
||||||
|
| `globe` | Lucide |
|
||||||
|
| `link` | Lucide |
|
||||||
|
| `rss` | Lucide |
|
||||||
|
| `calendar` | Lucide |
|
||||||
|
| `phone` | Lucide |
|
||||||
|
| `mappin` | Lucide |
|
||||||
|
| `filetext` | Lucide |
|
||||||
|
| `extlink` | Lucide |
|
||||||
|
| `share` | Lucide |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Icons are vendored at build time — no CDN fetch at runtime. Live gallery: **[tiennm99.github.io/bonsai/icons/](https://tiennm99.github.io/bonsai/icons/)**.
|
||||||
|
|
||||||
|
To refresh or add icons, edit `scripts/sync-icons.sh` and `data/icons.yaml`, then re-run the script. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/tiennm99/bonsai.git
|
||||||
|
cd bonsai/exampleSite
|
||||||
|
hugo server --themesDir ../.. --bind 0.0.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Build for inspection:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd exampleSite && hugo --themesDir ../.. --gc --minify
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, the icon-add workflow, and PR guidelines.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Apache-2.0 © [tiennm99](https://github.com/tiennm99). See [LICENSE](LICENSE) and [NOTICE](NOTICE) for third-party attributions (Simple Icons CC0, Lucide ISC).
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
+++
|
||||||
|
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||||
|
date = {{ .Date }}
|
||||||
|
draft = true
|
||||||
|
+++
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Bluesky</title><path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 662 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Buy Me A Coffee</title><path d="M20.216 6.415l-.132-.666c-.119-.598-.388-1.163-1.001-1.379-.197-.069-.42-.098-.57-.241-.152-.143-.196-.366-.231-.572-.065-.378-.125-.756-.192-1.133-.057-.325-.102-.69-.25-.987-.195-.4-.597-.634-.996-.788a5.723 5.723 0 00-.626-.194c-1-.263-2.05-.36-3.077-.416a25.834 25.834 0 00-3.7.062c-.915.083-1.88.184-2.75.5-.318.116-.646.256-.888.501-.297.302-.393.77-.177 1.146.154.267.415.456.692.58.36.162.737.284 1.123.366 1.075.238 2.189.331 3.287.37 1.218.05 2.437.01 3.65-.118.299-.033.598-.073.896-.119.352-.054.578-.513.474-.834-.124-.383-.457-.531-.834-.473-.466.074-.96.108-1.382.146-1.177.08-2.358.082-3.536.006a22.228 22.228 0 01-1.157-.107c-.086-.01-.18-.025-.258-.036-.243-.036-.484-.08-.724-.13-.111-.027-.111-.185 0-.212h.005c.277-.06.557-.108.838-.147h.002c.131-.009.263-.032.394-.048a25.076 25.076 0 013.426-.12c.674.019 1.347.067 2.017.144l.228.031c.267.04.533.088.798.145.392.085.895.113 1.07.542.055.137.08.288.111.431l.319 1.484a.237.237 0 01-.199.284h-.003c-.037.006-.075.01-.112.015a36.704 36.704 0 01-4.743.295 37.059 37.059 0 01-4.699-.304c-.14-.017-.293-.042-.417-.06-.326-.048-.649-.108-.973-.161-.393-.065-.768-.032-1.123.161-.29.16-.527.404-.675.701-.154.316-.199.66-.267 1-.069.34-.176.707-.135 1.056.087.753.613 1.365 1.37 1.502a39.69 39.69 0 0011.343.376.483.483 0 01.535.53l-.071.697-1.018 9.907c-.041.41-.047.832-.125 1.237-.122.637-.553 1.028-1.182 1.171-.577.131-1.165.2-1.756.205-.656.004-1.31-.025-1.966-.022-.699.004-1.556-.06-2.095-.58-.475-.458-.54-1.174-.605-1.793l-.731-7.013-.322-3.094c-.037-.351-.286-.695-.678-.678-.336.015-.718.3-.678.679l.228 2.185.949 9.112c.147 1.344 1.174 2.068 2.446 2.272.742.12 1.503.144 2.257.156.966.016 1.942.053 2.892-.122 1.408-.258 2.465-1.198 2.616-2.657.34-3.332.683-6.663 1.024-9.995l.215-2.087a.484.484 0 01.39-.426c.402-.078.787-.212 1.074-.518.455-.488.546-1.124.385-1.766zm-1.478.772c-.145.137-.363.201-.578.233-2.416.359-4.866.54-7.308.46-1.748-.06-3.477-.254-5.207-.498-.17-.024-.353-.055-.47-.18-.22-.236-.111-.71-.054-.995.052-.26.152-.609.463-.646.484-.057 1.046.148 1.526.22.577.088 1.156.159 1.737.212 2.48.226 5.002.19 7.472-.14.45-.06.899-.13 1.345-.21.399-.072.84-.206 1.08.206.166.281.188.657.162.974a.544.544 0 01-.169.364zm-6.159 3.9c-.862.37-1.84.788-3.109.788a5.884 5.884 0 01-1.569-.217l.877 9.004c.065.78.717 1.38 1.5 1.38 0 0 1.243.065 1.658.065.447 0 1.786-.065 1.786-.065.783 0 1.434-.6 1.499-1.38l.94-9.95a3.996 3.996 0 00-1.322-.238c-.826 0-1.491.284-2.26.613z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>dev.to</title><path d="M7.42 10.05c-.18-.16-.46-.23-.84-.23H6l.02 2.44.04 2.45.56-.02c.41 0 .63-.07.83-.26.24-.24.26-.36.26-2.2 0-1.91-.02-1.96-.29-2.18zM0 4.94v14.12h24V4.94H0zM8.56 15.3c-.44.58-1.06.77-2.53.77H4.71V8.53h1.4c1.67 0 2.16.18 2.6.9.27.43.29.6.32 2.57.05 2.23-.02 2.73-.47 3.3zm5.09-5.47h-2.47v1.77h1.52v1.28l-.72.04-.75.03v1.77l1.22.03 1.2.04v1.28h-1.6c-1.53 0-1.6-.01-1.87-.3l-.3-.28v-3.16c0-3.02.01-3.18.25-3.48.23-.31.25-.31 1.88-.31h1.64v1.3zm4.68 5.45c-.17.43-.64.79-1 .79-.18 0-.45-.15-.67-.39-.32-.32-.45-.63-.82-2.08l-.9-3.39-.45-1.67h.76c.4 0 .75.02.75.05 0 .06 1.16 4.54 1.26 4.83.04.15.32-.7.73-2.3l.66-2.52.74-.04c.4-.02.73 0 .73.04 0 .14-1.67 6.38-1.8 6.68z"/></svg>
|
||||||
|
After Width: | Height: | Size: 773 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discord</title><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Facebook</title><path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 543 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
||||||
|
After Width: | Height: | Size: 823 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitLab</title><path d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"/></svg>
|
||||||
|
After Width: | Height: | Size: 574 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Hashnode</title><path d="M22.351 8.019l-6.37-6.37a5.63 5.63 0 0 0-7.962 0l-6.37 6.37a5.63 5.63 0 0 0 0 7.962l6.37 6.37a5.63 5.63 0 0 0 7.962 0l6.37-6.37a5.63 5.63 0 0 0 0-7.962zM12 15.953a3.953 3.953 0 1 1 0-7.906 3.953 3.953 0 0 1 0 7.906z"/></svg>
|
||||||
|
After Width: | Height: | Size: 328 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Instagram</title><path d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Ko-fi</title><path d="M23.881 8.948c-.773-4.085-4.859-4.593-4.859-4.593H.723c-.604 0-.679.798-.679.798s-.082 7.324-.022 11.822c.164 2.424 2.586 2.672 2.586 2.672s8.267-.023 11.966-.049c2.438-.426 2.683-2.566 2.658-3.734 4.352.24 7.422-2.831 6.649-6.916zm-11.062 3.511c-1.246 1.453-4.011 3.976-4.011 3.976s-.121.119-.31.023c-.076-.057-.108-.09-.108-.09-.443-.441-3.368-3.049-4.034-3.954-.709-.965-1.041-2.7-.091-3.71.951-1.01 3.005-1.086 4.363.407 0 0 1.565-1.782 3.468-.963 1.904.82 1.832 3.011.723 4.311zm6.173.478c-.928.116-1.682.028-1.682.028V7.284h1.77s1.971.551 1.971 2.638c0 1.913-.985 2.667-2.059 3.015z"/></svg>
|
||||||
|
After Width: | Height: | Size: 698 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>LinkedIn</title><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
|
||||||
|
After Width: | Height: | Size: 611 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Mastodon</title><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Medium</title><path d="M13.54 12a6.8 6.8 0 01-6.77 6.82A6.8 6.8 0 010 12a6.8 6.8 0 016.77-6.82A6.8 6.8 0 0113.54 12zM20.96 12c0 3.54-1.51 6.42-3.38 6.42-1.87 0-3.39-2.88-3.39-6.42s1.52-6.42 3.39-6.42 3.38 2.88 3.38 6.42M24 12c0 3.17-.53 5.75-1.19 5.75-.66 0-1.19-2.58-1.19-5.75s.53-5.75 1.19-5.75C23.47 6.25 24 8.83 24 12z"/></svg>
|
||||||
|
After Width: | Height: | Size: 410 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Patreon</title><path d="M22.957 7.21c-.004-3.064-2.391-5.576-5.191-6.482-3.478-1.125-8.064-.962-11.384.604C2.357 3.231 1.093 7.391 1.046 11.54c-.039 3.411.302 12.396 5.369 12.46 3.765.047 4.326-4.804 6.068-7.141 1.24-1.662 2.836-2.132 4.801-2.618 3.376-.836 5.678-3.501 5.673-7.031Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 370 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>PayPal</title><path d="M7.016 19.198h-4.2a.562.562 0 0 1-.555-.65L5.093.584A.692.692 0 0 1 5.776 0h7.222c3.417 0 5.904 2.488 5.846 5.5-.006.25-.027.5-.066.747A6.794 6.794 0 0 1 12.071 12H8.743a.69.69 0 0 0-.682.583l-.325 2.056-.013.083-.692 4.39-.015.087zM19.79 6.142c-.01.087-.01.175-.023.261a7.76 7.76 0 0 1-7.695 6.598H9.007l-.283 1.795-.013.083-.692 4.39-.134.843-.014.088H6.86l-.497 3.15a.562.562 0 0 0 .555.65h3.612c.34 0 .63-.249.683-.585l.952-6.031a.692.692 0 0 1 .683-.584h2.126a6.793 6.793 0 0 0 6.707-5.752c.306-1.95-.466-3.744-1.89-4.906z"/></svg>
|
||||||
|
After Width: | Height: | Size: 638 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Reddit</title><path d="M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199c1.104 0 1.999.895 1.999 1.999 0 1.105-.895 2-1.999 2-.946 0-1.739-.657-1.947-1.539v.002c-1.147.162-2.032 1.15-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363.473-.363 1.064-.58 1.707-.58 1.547 0 2.802 1.254 2.802 2.802 0 1.117-.655 2.081-1.601 2.531-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87-.954-.447-1.614-1.415-1.614-2.538 0-1.548 1.255-2.802 2.803-2.802.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01c0-1.663 1.263-3.034 2.88-3.207.188-.911.993-1.595 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135-.147.015-.241.168-.183.305.483 1.154 1.622 1.964 2.953 1.964 1.33 0 2.47-.81 2.953-1.964.057-.137-.037-.29-.184-.305-.863-.087-1.795-.135-2.769-.135Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Signal</title><path d="m9.12.35.27 1.09a10.845 10.845 0 0 0-3.015 1.248l-.578-.964A11.955 11.955 0 0 1 9.12.35zm5.76 0-.27 1.09a10.845 10.845 0 0 1 3.015 1.248l.581-.964A11.955 11.955 0 0 0 14.88.35zM1.725 5.797A11.955 11.955 0 0 0 .351 9.119l1.09.27A10.845 10.845 0 0 1 2.69 6.374zm-.6 6.202a10.856 10.856 0 0 1 .122-1.63l-1.112-.168a12.043 12.043 0 0 0 0 3.596l1.112-.169A10.856 10.856 0 0 1 1.125 12zm17.078 10.275-.578-.964a10.845 10.845 0 0 1-3.011 1.247l.27 1.091a11.955 11.955 0 0 0 3.319-1.374zM22.875 12a10.856 10.856 0 0 1-.122 1.63l1.112.168a12.043 12.043 0 0 0 0-3.596l-1.112.169a10.856 10.856 0 0 1 .122 1.63zm.774 2.88-1.09-.27a10.845 10.845 0 0 1-1.248 3.015l.964.581a11.955 11.955 0 0 0 1.374-3.326zm-10.02 7.875a10.952 10.952 0 0 1-3.258 0l-.17 1.112a12.043 12.043 0 0 0 3.597 0zm7.125-4.303a10.914 10.914 0 0 1-2.304 2.302l.668.906a12.019 12.019 0 0 0 2.542-2.535zM18.45 3.245a10.914 10.914 0 0 1 2.304 2.304l.906-.675a12.019 12.019 0 0 0-2.535-2.535zM3.246 5.549A10.914 10.914 0 0 1 5.55 3.245l-.675-.906A12.019 12.019 0 0 0 2.34 4.874zm19.029.248-.964.577a10.845 10.845 0 0 1 1.247 3.011l1.091-.27a11.955 11.955 0 0 0-1.374-3.318zM10.371 1.246a10.952 10.952 0 0 1 3.258 0L13.8.134a12.043 12.043 0 0 0-3.597 0zM3.823 21.957 1.5 22.5l.542-2.323-1.095-.257-.542 2.323a1.125 1.125 0 0 0 1.352 1.352l2.321-.532zm-2.642-3.041 1.095.255.375-1.61a10.828 10.828 0 0 1-1.21-2.952l-1.09.27a11.91 11.91 0 0 0 1.106 2.852zm5.25 2.437-1.61.375.255 1.095 1.185-.275a11.91 11.91 0 0 0 2.851 1.106l.27-1.091a10.828 10.828 0 0 1-2.943-1.217zM12 2.25a9.75 9.75 0 0 0-8.25 14.938l-.938 4 4-.938A9.75 9.75 0 1 0 12 2.25z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Substack</title><path d="M22.539 8.242H1.46V5.406h21.08v2.836zM1.46 10.812V24L12 18.11 22.54 24V10.812H1.46zM22.54 0H1.46v2.836h21.08V0z"/></svg>
|
||||||
|
After Width: | Height: | Size: 224 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Telegram</title><path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg>
|
||||||
|
After Width: | Height: | Size: 743 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Threads</title><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>TikTok</title><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg>
|
||||||
|
After Width: | Height: | Size: 708 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitch</title><path d="M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 293 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>WhatsApp</title><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>X</title><path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 253 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>YouTube</title><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
||||||
|
After Width: | Height: | Size: 460 B |
@@ -0,0 +1,17 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M8 2v4" />
|
||||||
|
<path d="M16 2v4" />
|
||||||
|
<rect width="18" height="18" x="3" y="4" rx="2" />
|
||||||
|
<path d="M3 10h18" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 332 B |
@@ -0,0 +1,16 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M15 3h6v6" />
|
||||||
|
<path d="M10 14 21 3" />
|
||||||
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1,18 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
|
||||||
|
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
||||||
|
<path d="M10 9H8" />
|
||||||
|
<path d="M16 13H8" />
|
||||||
|
<path d="M16 17H8" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 394 B |
@@ -0,0 +1,16 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||||
|
<path d="M2 12h20" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 333 B |
@@ -0,0 +1,15 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
||||||
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 361 B |
@@ -0,0 +1,15 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<rect width="20" height="16" x="2" y="4" rx="2" />
|
||||||
|
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1,15 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" />
|
||||||
|
<circle cx="12" cy="10" r="3" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 361 B |
@@ -0,0 +1,14 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 511 B |
@@ -0,0 +1,16 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M4 11a9 9 0 0 1 9 9" />
|
||||||
|
<path d="M4 4a16 16 0 0 1 16 16" />
|
||||||
|
<circle cx="5" cy="19" r="1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 317 B |
@@ -0,0 +1,18 @@
|
|||||||
|
<svg
|
||||||
|
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="18" cy="5" r="3" />
|
||||||
|
<circle cx="6" cy="12" r="3" />
|
||||||
|
<circle cx="18" cy="19" r="3" />
|
||||||
|
<line x1="8.59" x2="15.42" y1="13.51" y2="17.49" />
|
||||||
|
<line x1="15.41" x2="8.59" y1="6.51" y2="10.49" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 420 B |
@@ -0,0 +1,44 @@
|
|||||||
|
# Icon manifest — maps public icon name → {family, slug}
|
||||||
|
# family: brand → assets/icons/brand/<slug>.svg (Simple Icons v13, CC0 1.0)
|
||||||
|
# family: ui → assets/icons/ui/<slug>.svg (Lucide Static v0.460, ISC)
|
||||||
|
#
|
||||||
|
# To add an icon: run scripts/sync-icons.sh then add an entry here.
|
||||||
|
|
||||||
|
# --- Brand / Social ---
|
||||||
|
github: { family: brand, slug: github }
|
||||||
|
gitlab: { family: brand, slug: gitlab }
|
||||||
|
mastodon: { family: brand, slug: mastodon }
|
||||||
|
bsky: { family: brand, slug: bluesky }
|
||||||
|
x: { family: brand, slug: x }
|
||||||
|
threads: { family: brand, slug: threads }
|
||||||
|
linkedin: { family: brand, slug: linkedin }
|
||||||
|
instagram: { family: brand, slug: instagram }
|
||||||
|
facebook: { family: brand, slug: facebook }
|
||||||
|
tiktok: { family: brand, slug: tiktok }
|
||||||
|
youtube: { family: brand, slug: youtube }
|
||||||
|
twitch: { family: brand, slug: twitch }
|
||||||
|
discord: { family: brand, slug: discord }
|
||||||
|
telegram: { family: brand, slug: telegram }
|
||||||
|
signal: { family: brand, slug: signal }
|
||||||
|
whatsapp: { family: brand, slug: whatsapp }
|
||||||
|
reddit: { family: brand, slug: reddit }
|
||||||
|
medium: { family: brand, slug: medium }
|
||||||
|
devto: { family: brand, slug: devdotto }
|
||||||
|
substack: { family: brand, slug: substack }
|
||||||
|
hashnode: { family: brand, slug: hashnode }
|
||||||
|
kofi: { family: brand, slug: kofi }
|
||||||
|
patreon: { family: brand, slug: patreon }
|
||||||
|
buymeacoffee: { family: brand, slug: buymeacoffee }
|
||||||
|
paypal: { family: brand, slug: paypal }
|
||||||
|
|
||||||
|
# --- UI / Utility ---
|
||||||
|
mail: { family: ui, slug: mail }
|
||||||
|
globe: { family: ui, slug: globe }
|
||||||
|
link: { family: ui, slug: link }
|
||||||
|
rss: { family: ui, slug: rss }
|
||||||
|
calendar: { family: ui, slug: calendar }
|
||||||
|
phone: { family: ui, slug: phone }
|
||||||
|
mappin: { family: ui, slug: map-pin }
|
||||||
|
filetext: { family: ui, slug: file-text }
|
||||||
|
extlink: { family: ui, slug: external-link }
|
||||||
|
share: { family: ui, slug: share-2 }
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Bonsai v0.1: Icon Library Split Forced Mid-Course Correction
|
||||||
|
|
||||||
|
**Date**: 2026-05-03 14:30
|
||||||
|
**Severity**: Medium
|
||||||
|
**Component**: Icon system, asset paths, demo deployment
|
||||||
|
**Status**: Resolved
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
Shipped Bonsai v0.1.0 to Hugo themes catalog in one day: icon system overhaul (Lucide + Simple Icons vendoring), CI/CD pipeline, GitHub Pages demo, docs, submission assets, and PR #708 to gohugoio. All 6 phases executed end-to-end with 11 commits. Live at https://tiennm99.github.io/bonsai/; catalog PR awaiting review.
|
||||||
|
|
||||||
|
## The Brutal Truth
|
||||||
|
|
||||||
|
Shipping a theme on spec in 15 hours felt clean until we deployed. The first three surprises weren't fatal but exposed how little we knew about Hugo's URL handling under GitHub Pages subpaths and how Chromium renders fonts in CI. Each one cost a fix commit. The Japanese avatar glyph as tofu in a headless browser was funny; the SVGs rendering at 0×0 was not.
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
1. **Icon library split enforcement**: Lucide intentionally omits brand/social icons (design principle). Simple Icons provides them but ships SVGs with only `viewBox`, no intrinsic dimensions. First screenshot: brand icons invisible. Fix: `.link__icon svg { width:20px; height:20px }` in `assets/css/style.css`.
|
||||||
|
|
||||||
|
2. **Hugo URL function behavior under subpaths**: `relURL` and `absURL` pass through leading-slash inputs unchanged, which bypasses the baseURL subpath. Asset paths broke on `tiennm99.github.io/bonsai/static/*`. Fix: strip leading slash before `relURL`: `strings.TrimPrefix "/" $path | relURL` in `partials/icon.html` and layout.
|
||||||
|
|
||||||
|
3. **Chromium headless rendering**: No Japanese fonts in Docker env → 盆 monogram rendered as tofu boxes. Switched to Latin "T" in avatar. Future: any glyph-specific UX needs SVG paths or pre-rendered images.
|
||||||
|
|
||||||
|
4. **Playwright Docker deps**: Headless Chromium needs system libs absent from base image: `libnss3 libnspr4 libatk1.0-0t64 libatspi2.0-0t64 libxcomposite1 libxdamage1 libcups2t64`. Documented in `scripts/capture-screenshots.sh`.
|
||||||
|
|
||||||
|
## What We Tried
|
||||||
|
|
||||||
|
- Initial icon partial used inline SVGs only (8 icons) — ran out of coverage, switched to two-source vendoring.
|
||||||
|
- Attempted to use Lucide as single source — failed (no brand icons by design).
|
||||||
|
- First deploy: brand icons invisible in screenshots — diagnostic: SVGs had viewBox only, zero dimensions in flex layout.
|
||||||
|
- Japanese avatar: rendered in local dev, broke in CI — switching glyph is safer than shipping fonts.
|
||||||
|
|
||||||
|
## Root Cause Analysis
|
||||||
|
|
||||||
|
**Icon library research was shallow.** The plan correctly identified the split strategy, but we didn't validate that Simple Icons SVGs lack intrinsic dimensions until deploy. A quick inspection of one SVG from each library before implementation would have caught this.
|
||||||
|
|
||||||
|
**URL function assumptions didn't account for subpath deploys.** Standard Hugo docs assume domain root. GitHub Pages subpaths require different handling. Should've tested asset paths locally with `baseURL: /bonsai/` before CI.
|
||||||
|
|
||||||
|
**No validation that Chromium env matched design expectations.** The avatar glyph choice was visual-first; zero verification that it would render in the CI environment.
|
||||||
|
|
||||||
|
## Lessons Learned
|
||||||
|
|
||||||
|
1. **Inspect library assets, don't trust API docs.** Download one SVG from each icon library, check for width/height attributes, test in a flex container.
|
||||||
|
|
||||||
|
2. **Test baseURL subpaths locally.** Hugo's GitHub Pages subpath handling is a gotcha. Always `hugo server --baseURL http://localhost:1313/bonsai/` before deploy.
|
||||||
|
|
||||||
|
3. **Headless rendering != local rendering.** Fonts, system libs, and environment assumptions bite hard. Build and screenshot in CI early, not on final push.
|
||||||
|
|
||||||
|
4. **Document dependency chains in scripts.** The Playwright setup needed specific system packages. Future: add precheck or include install hints in README.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Monitor PR #708 review feedback from Hugo catalog maintainers.
|
||||||
|
- If layout changes are requested, we have the icon system and CI infrastructure in place to iterate.
|
||||||
|
- For v1.0: consider layout variants (cards/grid) — the data-driven icon partial scales well.
|
||||||
|
- Keep screenshot capture script maintenance low: Playwright + Chromium is reliable once deps are documented.
|
||||||
|
|
||||||
|
**Owner**: @tiennm99
|
||||||
|
**Timeline**: v0.2 planning after catalog acceptance feedback
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Home"
|
||||||
|
---
|
||||||
|
|
||||||
|
This file exists so Hugo renders the index page. All bio content comes from `[params]` in `hugo.toml`.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: "Icon Gallery"
|
||||||
|
description: "All available icons in the Bonsai theme."
|
||||||
|
layout: "single"
|
||||||
|
type: "icons"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Color themes"
|
||||||
|
type: themes
|
||||||
|
---
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
baseURL = "https://example.com/"
|
||||||
|
languageCode = "en-us"
|
||||||
|
title = "Tien Nguyen"
|
||||||
|
theme = "bonsai"
|
||||||
|
|
||||||
|
# Bonsai is a single-page bio theme — no need for taxonomies, sitemaps, or RSS.
|
||||||
|
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "404"]
|
||||||
|
|
||||||
|
[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 omitted intentionally so the demo showcases the initials fallback
|
||||||
|
# (the SVG circle uses the active palette's accent color)
|
||||||
|
colorTheme = "sakura" # try "bonsai" (default), "sakura", "sumi", "koi"
|
||||||
|
themeToggle = true
|
||||||
|
jobTitle = "Software Engineer"
|
||||||
|
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"
|
||||||
|
|
||||||
|
[[params.links]]
|
||||||
|
title = "Mastodon"
|
||||||
|
url = "https://mastodon.social/@tiennm99"
|
||||||
|
icon = "mastodon"
|
||||||
|
|
||||||
|
[[params.links]]
|
||||||
|
title = "Bluesky"
|
||||||
|
url = "https://bsky.app/profile/tiennm99.bsky.social"
|
||||||
|
icon = "bsky"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
|
||||||
|
<circle cx="100" cy="100" r="100" fill="#8b3a2b"/>
|
||||||
|
<text x="100" y="138" font-family="Georgia, serif" font-size="110" font-weight="600" fill="#f4efe6" text-anchor="middle">T</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 280 B |
@@ -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
|
||||||
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ site.LanguageCode | default `en` }}" data-theme="auto" data-bonsai-theme="{{ site.Params.colorTheme | default `bonsai` }}">
|
||||||
|
<head>
|
||||||
|
{{- partial "head.html" . -}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="bonsai">
|
||||||
|
{{- block "main" . }}{{- end }}
|
||||||
|
</main>
|
||||||
|
{{- partial "footer.html" . -}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
<section class="icon-gallery">
|
||||||
|
<h1>{{ .Title }}</h1>
|
||||||
|
<p>{{ .Description | default "All available icons in the Bonsai theme." }}</p>
|
||||||
|
<div style="display:flex;flex-wrap:wrap;gap:1.5rem;margin-top:1.5rem;">
|
||||||
|
{{- range $name, $entry := site.Data.icons }}
|
||||||
|
<div style="display:flex;flex-direction:column;align-items:center;gap:0.4rem;width:80px;">
|
||||||
|
<span style="width:20px;height:20px;">{{ partial "icon.html" $name }}</span>
|
||||||
|
<span style="font-size:0.7rem;text-align:center;word-break:break-all;">{{ $name }}</span>
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
{{- partial "bio-card.html" . -}}
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{{- /* Render bio avatar.
|
||||||
|
If params.avatar is set, render <img>.
|
||||||
|
Otherwise, render an inline SVG circle with initials in accent color.
|
||||||
|
Initials override: params.avatarInitials.
|
||||||
|
Background override: params.avatarBg (defaults to --bonsai-accent). */ -}}
|
||||||
|
{{- $name := site.Params.name | default site.Title -}}
|
||||||
|
{{- $avatar := site.Params.avatar -}}
|
||||||
|
|
||||||
|
{{- if $avatar -}}
|
||||||
|
<img class="bio__avatar" src="{{ $avatar | strings.TrimPrefix "/" | relURL }}" alt="{{ $name }}" loading="eager" />
|
||||||
|
{{- else -}}
|
||||||
|
{{- $initials := site.Params.avatarInitials -}}
|
||||||
|
{{- if not $initials -}}
|
||||||
|
{{- $words := split (strings.TrimSpace $name) " " -}}
|
||||||
|
{{- $first := "" -}}
|
||||||
|
{{- $second := "" -}}
|
||||||
|
{{- with index $words 0 -}}{{- $first = substr . 0 1 -}}{{- end -}}
|
||||||
|
{{- if gt (len $words) 1 -}}{{- $second = substr (index $words 1) 0 1 -}}{{- end -}}
|
||||||
|
{{- $initials = upper (printf "%s%s" $first $second) -}}
|
||||||
|
{{- if not $initials -}}{{- $initials = "?" -}}{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- $bg := site.Params.avatarBg | default "var(--bonsai-accent)" -}}
|
||||||
|
<svg class="bio__avatar bio__avatar--initials" viewBox="0 0 112 112" width="112" height="112" role="img" aria-label="{{ $name }}">
|
||||||
|
<circle cx="56" cy="56" r="56" fill="{{ $bg }}"/>
|
||||||
|
<text x="56" y="74" text-anchor="middle"
|
||||||
|
font-family="ui-serif, Georgia, serif"
|
||||||
|
font-size="56" font-weight="600"
|
||||||
|
fill="var(--bonsai-bg)">{{ $initials }}</text>
|
||||||
|
</svg>
|
||||||
|
{{- end -}}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{{- $name := site.Params.name | default site.Title -}}
|
||||||
|
{{- $tagline := site.Params.tagline -}}
|
||||||
|
{{- $bio := site.Params.bio -}}
|
||||||
|
{{- $links := site.Params.links -}}
|
||||||
|
|
||||||
|
<article class="bio">
|
||||||
|
{{ partial "avatar.html" . }}
|
||||||
|
|
||||||
|
<h1 class="bio__name">{{ $name }}</h1>
|
||||||
|
|
||||||
|
{{- with $tagline }}
|
||||||
|
<p class="bio__tagline">{{ . }}</p>
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- with $bio }}
|
||||||
|
<p class="bio__text">{{ . | markdownify }}</p>
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if $links }}
|
||||||
|
<nav class="bio__links" aria-label="Links">
|
||||||
|
{{- range $links }}
|
||||||
|
{{- partial "link-button.html" . -}}
|
||||||
|
{{- end }}
|
||||||
|
</nav>
|
||||||
|
{{- end }}
|
||||||
|
</article>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{{- if site.Params.footer | default true -}}
|
||||||
|
<footer class="bonsai-footer">
|
||||||
|
{{- with site.Params.footerText -}}
|
||||||
|
<p>{{ . | safeHTML }}</p>
|
||||||
|
{{- else -}}
|
||||||
|
<p>© {{ now.Year }} {{ site.Params.name | default site.Title }}</p>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if site.Params.themeToggle -}}
|
||||||
|
{{ partial "theme-toggle-button.html" . }}
|
||||||
|
{{- end -}}
|
||||||
|
</footer>
|
||||||
|
{{- end -}}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{{- $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 -}}
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<title>{{ $name }}{{ with $tagline }} — {{ . }}{{ end }}</title>
|
||||||
|
{{- with $description }}
|
||||||
|
<meta name="description" content="{{ . }}" />
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
<meta property="og:type" content="profile" />
|
||||||
|
<meta property="og:title" content="{{ $name }}" />
|
||||||
|
{{- with $description }}
|
||||||
|
<meta property="og:description" content="{{ . }}" />
|
||||||
|
{{- end }}
|
||||||
|
{{- with $avatar }}
|
||||||
|
<meta property="og:image" content="{{ . | absURL }}" />
|
||||||
|
{{- end }}
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
|
||||||
|
{{ partial "schema-person.html" . }}
|
||||||
|
|
||||||
|
<link rel="icon" href="{{ (site.Params.favicon | default `favicon.ico`) | strings.TrimPrefix "/" | relURL }}" />
|
||||||
|
<link rel="stylesheet" href="{{ `css/bonsai.css` | relURL }}" />
|
||||||
|
{{- if site.Params.themeToggle }}
|
||||||
|
<script defer src="{{ `js/theme-toggle.js` | relURL }}"></script>
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{{- $name := . -}}
|
||||||
|
{{- $entry := index site.Data.icons $name -}}
|
||||||
|
{{- if $entry -}}
|
||||||
|
{{- $path := printf "icons/%s/%s.svg" $entry.family $entry.slug -}}
|
||||||
|
{{- with resources.Get $path -}}
|
||||||
|
{{- .Content | safeHTML -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- /* asset missing — fall through to fallback below */ -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- /* Unknown icon name: render generic external-link icon */ -}}
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" xmlns="http://www.w3.org/2000/svg"><path d="M10 14a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5M14 10a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg>
|
||||||
|
{{- end -}}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{{- $title := .title -}}
|
||||||
|
{{- $url := .url -}}
|
||||||
|
{{- $icon := .icon -}}
|
||||||
|
{{- $external := strings.HasPrefix $url "http" -}}
|
||||||
|
<a class="link"
|
||||||
|
href="{{ $url }}"
|
||||||
|
{{- if $external }} target="_blank" rel="noopener noreferrer"{{ end }}>
|
||||||
|
{{- with $icon }}
|
||||||
|
<span class="link__icon" aria-hidden="true">{{ partial "icon.html" . }}</span>
|
||||||
|
{{- end }}
|
||||||
|
<span class="link__title">{{ $title }}</span>
|
||||||
|
</a>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{{- /* Emit JSON-LD ProfilePage describing the bio as a Person.
|
||||||
|
Suppressed when site.Params.schema = false. */ -}}
|
||||||
|
{{- if ne (site.Params.schema | default true) false -}}
|
||||||
|
{{- $name := site.Params.name | default site.Title -}}
|
||||||
|
{{- $sameAs := slice -}}
|
||||||
|
{{- range site.Params.links -}}
|
||||||
|
{{- with .url -}}
|
||||||
|
{{- if not (or (strings.HasPrefix . "mailto:") (strings.HasPrefix . "tel:")) -}}
|
||||||
|
{{- $sameAs = $sameAs | append . -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- $person := dict
|
||||||
|
"@type" "Person"
|
||||||
|
"name" $name
|
||||||
|
"url" (site.BaseURL | absURL)
|
||||||
|
-}}
|
||||||
|
{{- with site.Params.bio -}}{{- $person = merge $person (dict "description" .) -}}{{- end -}}
|
||||||
|
{{- with site.Params.avatar -}}
|
||||||
|
{{- $person = merge $person (dict "image" (. | strings.TrimPrefix "/" | absURL)) -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- with site.Params.jobTitle -}}{{- $person = merge $person (dict "jobTitle" .) -}}{{- end -}}
|
||||||
|
{{- with site.Params.location -}}{{- $person = merge $person (dict "address" .) -}}{{- end -}}
|
||||||
|
{{- with site.Params.email -}}{{- $person = merge $person (dict "email" .) -}}{{- end -}}
|
||||||
|
{{- if $sameAs -}}{{- $person = merge $person (dict "sameAs" $sameAs) -}}{{- end -}}
|
||||||
|
{{- $payload := dict "@context" "https://schema.org" "@type" "ProfilePage" "mainEntity" $person -}}
|
||||||
|
<script type="application/ld+json">{{ $payload | jsonify | safeJS }}</script>
|
||||||
|
{{- end -}}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{{- /* Sun/moon theme toggle. Only loaded/rendered when site.Params.themeToggle = true.
|
||||||
|
Sun shown in dark mode (target = light). Moon shown in light mode (target = dark). */ -}}
|
||||||
|
<button type="button" class="theme-toggle"
|
||||||
|
data-bonsai-theme-toggle
|
||||||
|
aria-label="Toggle light and dark theme"
|
||||||
|
aria-pressed="false"
|
||||||
|
title="Toggle theme">
|
||||||
|
<svg class="theme-toggle__sun" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
|
||||||
|
<circle cx="12" cy="12" r="4" fill="currentColor"/>
|
||||||
|
<g stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||||
|
<line x1="12" y1="2" x2="12" y2="5"/>
|
||||||
|
<line x1="12" y1="19" x2="12" y2="22"/>
|
||||||
|
<line x1="2" y1="12" x2="5" y2="12"/>
|
||||||
|
<line x1="19" y1="12" x2="22" y2="12"/>
|
||||||
|
<line x1="4.9" y1="4.9" x2="7" y2="7"/>
|
||||||
|
<line x1="17" y1="17" x2="19.1" y2="19.1"/>
|
||||||
|
<line x1="4.9" y1="19.1" x2="7" y2="17"/>
|
||||||
|
<line x1="17" y1="7" x2="19.1" y2="4.9"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<svg class="theme-toggle__moon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
|
||||||
|
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
<article style="text-align:center;">
|
||||||
|
<h1 style="font-family:var(--bonsai-font-display);">Color themes</h1>
|
||||||
|
<p style="color:var(--bonsai-muted);max-width:32rem;margin:0 auto 2rem;">
|
||||||
|
Set <code>colorTheme = "bonsai"</code> in your <code>hugo.toml</code> <code>[params]</code> block. Each palette has light + dark variants.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{{- $palettes := slice
|
||||||
|
(dict "name" "bonsai" "label" "Bonsai (washi + vermilion)" "accent" "#8b3a2b")
|
||||||
|
(dict "name" "sakura" "label" "Sakura (cherry blossom)" "accent" "#d4456a")
|
||||||
|
(dict "name" "sumi" "label" "Sumi (monochrome ink)" "accent" "#1a1a1a")
|
||||||
|
(dict "name" "koi" "label" "Koi (orange + cream)" "accent" "#c8521e")
|
||||||
|
-}}
|
||||||
|
|
||||||
|
<div class="themes-gallery">
|
||||||
|
{{- range $palettes }}
|
||||||
|
<div data-bonsai-theme="{{ .name }}" class="themes-gallery__card">
|
||||||
|
<h3 class="themes-gallery__name">{{ .label }}</h3>
|
||||||
|
<p class="themes-gallery__hex">accent {{ .accent }}</p>
|
||||||
|
<span class="themes-gallery__chip">link button</span>
|
||||||
|
<span class="themes-gallery__chip themes-gallery__chip--accent">accent</span>
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Capture submission assets (images/screenshot.png + images/tn.png) from
|
||||||
|
# the live demo. Run from repo root. Re-runnable; overwrites existing files.
|
||||||
|
#
|
||||||
|
# Requires: npx (installs Playwright on first run, ~170 MB chromium cache)
|
||||||
|
# Optional: oxipng (lossless PNG compression — recommended)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
URL="${BONSAI_DEMO_URL:-https://tiennm99.github.io/bonsai/}"
|
||||||
|
SCREENSHOT="images/screenshot.png"
|
||||||
|
THUMBNAIL="images/tn.png"
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
mkdir -p images
|
||||||
|
|
||||||
|
echo "==> Installing Playwright Chromium (cached after first run)"
|
||||||
|
npx --yes playwright@1.50.0 install chromium
|
||||||
|
|
||||||
|
echo "==> Capturing screenshot.png (1500x1000)"
|
||||||
|
npx --yes playwright@1.50.0 screenshot \
|
||||||
|
--viewport-size=1500,1000 \
|
||||||
|
--browser=chromium \
|
||||||
|
--color-scheme=light \
|
||||||
|
--wait-for-timeout=800 \
|
||||||
|
"$URL" "$SCREENSHOT"
|
||||||
|
|
||||||
|
echo "==> Capturing tn.png (900x600)"
|
||||||
|
npx --yes playwright@1.50.0 screenshot \
|
||||||
|
--viewport-size=900,600 \
|
||||||
|
--browser=chromium \
|
||||||
|
--color-scheme=light \
|
||||||
|
--wait-for-timeout=800 \
|
||||||
|
"$URL" "$THUMBNAIL"
|
||||||
|
|
||||||
|
if command -v oxipng >/dev/null 2>&1; then
|
||||||
|
echo "==> Compressing with oxipng"
|
||||||
|
oxipng -o4 --strip safe "$SCREENSHOT" "$THUMBNAIL"
|
||||||
|
else
|
||||||
|
echo "==> oxipng not installed; skipping compression (install for smaller files)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Done"
|
||||||
|
file "$SCREENSHOT" "$THUMBNAIL"
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# sync-icons.sh — vendor Simple Icons (brand) + Lucide (ui) SVGs into assets/icons/
|
||||||
|
# Idempotent: safe to re-run; existing files are overwritten.
|
||||||
|
#
|
||||||
|
# Sources (pinned):
|
||||||
|
# Simple Icons v13.0.0 — CC0 1.0 Universal
|
||||||
|
# Lucide Static v0.460.0 — ISC License
|
||||||
|
#
|
||||||
|
# Usage: bash scripts/sync-icons.sh
|
||||||
|
# Run from repo root.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SI_VERSION="13.0.0"
|
||||||
|
LUCIDE_VERSION="0.460.0"
|
||||||
|
|
||||||
|
SI_BASE="https://cdn.jsdelivr.net/npm/simple-icons@${SI_VERSION}/icons"
|
||||||
|
LUCIDE_BASE="https://cdn.jsdelivr.net/npm/lucide-static@${LUCIDE_VERSION}/icons"
|
||||||
|
|
||||||
|
BRAND_DIR="assets/icons/brand"
|
||||||
|
UI_DIR="assets/icons/ui"
|
||||||
|
|
||||||
|
mkdir -p "${BRAND_DIR}" "${UI_DIR}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Simple Icons brand set (~25 icons)
|
||||||
|
# Simple Icons use fill="..." with the brand color; we strip it so the icon
|
||||||
|
# inherits currentColor from its CSS context.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
BRAND_ICONS=(
|
||||||
|
github gitlab mastodon bluesky x threads linkedin instagram facebook
|
||||||
|
tiktok youtube twitch discord telegram signal whatsapp reddit medium
|
||||||
|
devdotto substack hashnode kofi patreon buymeacoffee paypal
|
||||||
|
)
|
||||||
|
|
||||||
|
fetch_brand() {
|
||||||
|
local slug="$1"
|
||||||
|
local dest="${BRAND_DIR}/${slug}.svg"
|
||||||
|
local url="${SI_BASE}/${slug}.svg"
|
||||||
|
local tmp
|
||||||
|
|
||||||
|
tmp="$(curl -fsSL "${url}")" || { echo " WARN: failed to fetch ${url}" >&2; return; }
|
||||||
|
|
||||||
|
# Strip hardcoded fill attribute from <svg> tag and any <path> elements.
|
||||||
|
# Simple Icons SVGs have fill="#XXXXXX" on the root <svg>.
|
||||||
|
# Replace with fill="currentColor".
|
||||||
|
echo "${tmp}" \
|
||||||
|
| sed 's/fill="#[0-9A-Fa-f]\{3,6\}"/fill="currentColor"/g' \
|
||||||
|
| sed "s/fill='#[0-9A-Fa-f]\{3,6\}'/fill=\"currentColor\"/g" \
|
||||||
|
> "${dest}"
|
||||||
|
|
||||||
|
echo " brand/${slug}.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Fetching Simple Icons v${SI_VERSION}..."
|
||||||
|
for slug in "${BRAND_ICONS[@]}"; do
|
||||||
|
fetch_brand "${slug}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Lucide UI icon set (~10 icons)
|
||||||
|
# Lucide SVGs already use stroke="currentColor"; we add width/height="20"
|
||||||
|
# to normalise size and remove the class attribute (not needed for inline use).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
UI_ICONS=(
|
||||||
|
mail globe link rss calendar phone map-pin file-text external-link share-2
|
||||||
|
)
|
||||||
|
|
||||||
|
fetch_ui() {
|
||||||
|
local slug="$1"
|
||||||
|
local dest="${UI_DIR}/${slug}.svg"
|
||||||
|
local url="${LUCIDE_BASE}/${slug}.svg"
|
||||||
|
local tmp
|
||||||
|
|
||||||
|
tmp="$(curl -fsSL "${url}")" || { echo " WARN: failed to fetch ${url}" >&2; return; }
|
||||||
|
|
||||||
|
# Normalise: set width/height to 20, remove class attribute, remove comment line.
|
||||||
|
echo "${tmp}" \
|
||||||
|
| grep -v '^<!--' \
|
||||||
|
| sed 's/width="24"/width="20"/g' \
|
||||||
|
| sed 's/height="24"/height="20"/g' \
|
||||||
|
| sed 's/ class="[^"]*"//g' \
|
||||||
|
> "${dest}"
|
||||||
|
|
||||||
|
echo " ui/${slug}.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Fetching Lucide Static v${LUCIDE_VERSION}..."
|
||||||
|
for slug in "${UI_ICONS[@]}"; do
|
||||||
|
fetch_ui "${slug}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done. Vendored $(ls "${BRAND_DIR}"/*.svg 2>/dev/null | wc -l | tr -d ' ') brand icons, $(ls "${UI_DIR}"/*.svg 2>/dev/null | wc -l | tr -d ' ') ui icons."
|
||||||
@@ -0,0 +1,381 @@
|
|||||||
|
/* Bonsai — minimalist link-in-bio Hugo theme
|
||||||
|
* Aesthetic: Japanese zen — generous whitespace, calm palette, restrained type.
|
||||||
|
* Palettes: bonsai (default), sakura, sumi, koi. Selected via [data-bonsai-theme]. */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Palettes
|
||||||
|
Each palette defines six color vars in light + dark variants.
|
||||||
|
Toggle order:
|
||||||
|
1. base block sets light values
|
||||||
|
2. @media (prefers-color-scheme:dark) overrides to dark
|
||||||
|
3. [data-theme="dark"] forces dark (manual toggle)
|
||||||
|
4. [data-theme="light"] forces light (manual toggle)
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* --- Bonsai (default: washi + vermilion) --- */
|
||||||
|
:root,
|
||||||
|
[data-bonsai-theme="bonsai"] {
|
||||||
|
--bonsai-bg: #f4efe6;
|
||||||
|
--bonsai-surface: #ffffff;
|
||||||
|
--bonsai-text: #2b2b2b;
|
||||||
|
--bonsai-muted: #6b6b6b;
|
||||||
|
--bonsai-accent: #8b3a2b;
|
||||||
|
--bonsai-border: #d9d0bf;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root:not([data-theme="light"]):not([data-bonsai-theme]),
|
||||||
|
[data-bonsai-theme="bonsai"]:not([data-theme="light"]) {
|
||||||
|
--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="dark"]:not([data-bonsai-theme]),
|
||||||
|
[data-bonsai-theme="bonsai"][data-theme="dark"] {
|
||||||
|
--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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Sakura (cherry blossom) --- */
|
||||||
|
[data-bonsai-theme="sakura"] {
|
||||||
|
--bonsai-bg: #fff5f5;
|
||||||
|
--bonsai-surface: #ffffff;
|
||||||
|
--bonsai-text: #3a2228;
|
||||||
|
--bonsai-muted: #876773;
|
||||||
|
--bonsai-accent: #d4456a;
|
||||||
|
--bonsai-border: #f0d4dc;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
[data-bonsai-theme="sakura"]:not([data-theme="light"]) {
|
||||||
|
--bonsai-bg: #1f1418;
|
||||||
|
--bonsai-surface: #2a1d22;
|
||||||
|
--bonsai-text: #f7e3e9;
|
||||||
|
--bonsai-muted: #b89aa3;
|
||||||
|
--bonsai-accent: #ec7596;
|
||||||
|
--bonsai-border: #3d2932;
|
||||||
|
--bonsai-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-bonsai-theme="sakura"][data-theme="dark"] {
|
||||||
|
--bonsai-bg: #1f1418;
|
||||||
|
--bonsai-surface: #2a1d22;
|
||||||
|
--bonsai-text: #f7e3e9;
|
||||||
|
--bonsai-muted: #b89aa3;
|
||||||
|
--bonsai-accent: #ec7596;
|
||||||
|
--bonsai-border: #3d2932;
|
||||||
|
--bonsai-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Sumi (monochrome ink) --- */
|
||||||
|
[data-bonsai-theme="sumi"] {
|
||||||
|
--bonsai-bg: #fafafa;
|
||||||
|
--bonsai-surface: #ffffff;
|
||||||
|
--bonsai-text: #111111;
|
||||||
|
--bonsai-muted: #666666;
|
||||||
|
--bonsai-accent: #1a1a1a;
|
||||||
|
--bonsai-border: #e0e0e0;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
[data-bonsai-theme="sumi"]:not([data-theme="light"]) {
|
||||||
|
--bonsai-bg: #0d0d0d;
|
||||||
|
--bonsai-surface: #1a1a1a;
|
||||||
|
--bonsai-text: #f5f5f5;
|
||||||
|
--bonsai-muted: #a0a0a0;
|
||||||
|
--bonsai-accent: #dddddd;
|
||||||
|
--bonsai-border: #2a2a2a;
|
||||||
|
--bonsai-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-bonsai-theme="sumi"][data-theme="dark"] {
|
||||||
|
--bonsai-bg: #0d0d0d;
|
||||||
|
--bonsai-surface: #1a1a1a;
|
||||||
|
--bonsai-text: #f5f5f5;
|
||||||
|
--bonsai-muted: #a0a0a0;
|
||||||
|
--bonsai-accent: #dddddd;
|
||||||
|
--bonsai-border: #2a2a2a;
|
||||||
|
--bonsai-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Koi (orange + cream) --- */
|
||||||
|
[data-bonsai-theme="koi"] {
|
||||||
|
--bonsai-bg: #fef6e4;
|
||||||
|
--bonsai-surface: #ffffff;
|
||||||
|
--bonsai-text: #3a2620;
|
||||||
|
--bonsai-muted: #8a6f5c;
|
||||||
|
--bonsai-accent: #c8521e;
|
||||||
|
--bonsai-border: #f2dfb8;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
[data-bonsai-theme="koi"]:not([data-theme="light"]) {
|
||||||
|
--bonsai-bg: #1c1410;
|
||||||
|
--bonsai-surface: #2a1f18;
|
||||||
|
--bonsai-text: #f7e2c4;
|
||||||
|
--bonsai-muted: #c9a988;
|
||||||
|
--bonsai-accent: #ff8b5c;
|
||||||
|
--bonsai-border: #3d2c20;
|
||||||
|
--bonsai-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-bonsai-theme="koi"][data-theme="dark"] {
|
||||||
|
--bonsai-bg: #1c1410;
|
||||||
|
--bonsai-surface: #2a1f18;
|
||||||
|
--bonsai-text: #f7e2c4;
|
||||||
|
--bonsai-muted: #c9a988;
|
||||||
|
--bonsai-accent: #ff8b5c;
|
||||||
|
--bonsai-border: #3d2c20;
|
||||||
|
--bonsai-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Layout & components
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
* { 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__avatar--initials {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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__icon svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: block;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lucide UI icons use stroke, not fill */
|
||||||
|
.link__icon svg[stroke="currentColor"],
|
||||||
|
.link__icon svg.lucide {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link__title { line-height: 1; }
|
||||||
|
|
||||||
|
.bonsai-footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem var(--bonsai-pad) 2rem;
|
||||||
|
color: var(--bonsai-muted);
|
||||||
|
font-size: .8rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonsai-footer p { margin: 0; }
|
||||||
|
|
||||||
|
/* Theme toggle button */
|
||||||
|
.theme-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--bonsai-surface);
|
||||||
|
color: var(--bonsai-accent);
|
||||||
|
border: 1px solid var(--bonsai-border);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform .15s ease, border-color .15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
border-color: var(--bonsai-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle:focus-visible {
|
||||||
|
outline: 2px solid var(--bonsai-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show moon in light, sun in dark.
|
||||||
|
The button shows the icon for the mode you'll switch TO.
|
||||||
|
Per-class rules avoid the specificity battle vs `.theme-toggle svg`. */
|
||||||
|
.theme-toggle .theme-toggle__sun { display: none; }
|
||||||
|
.theme-toggle .theme-toggle__moon { display: block; }
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
html:not([data-theme="light"]) .theme-toggle .theme-toggle__sun { display: block; }
|
||||||
|
html:not([data-theme="light"]) .theme-toggle .theme-toggle__moon { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .theme-toggle .theme-toggle__sun { display: block; }
|
||||||
|
[data-theme="dark"] .theme-toggle .theme-toggle__moon { display: none; }
|
||||||
|
[data-theme="light"] .theme-toggle .theme-toggle__sun { display: none; }
|
||||||
|
[data-theme="light"] .theme-toggle .theme-toggle__moon { display: block; }
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Themes gallery (exampleSite/themes/) — 4 mini bio cards
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.themes-gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
max-width: 60rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.themes-gallery__card {
|
||||||
|
background: var(--bonsai-bg);
|
||||||
|
border: 1px solid var(--bonsai-border);
|
||||||
|
border-radius: var(--bonsai-radius);
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.themes-gallery__name {
|
||||||
|
font-family: var(--bonsai-font-display);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 0 0 .25rem;
|
||||||
|
color: var(--bonsai-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.themes-gallery__hex {
|
||||||
|
color: var(--bonsai-muted);
|
||||||
|
font-family: ui-monospace, monospace;
|
||||||
|
font-size: .75rem;
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.themes-gallery__chip {
|
||||||
|
display: inline-block;
|
||||||
|
padding: .4rem .75rem;
|
||||||
|
background: var(--bonsai-surface);
|
||||||
|
color: var(--bonsai-text);
|
||||||
|
border: 1px solid var(--bonsai-border);
|
||||||
|
border-radius: var(--bonsai-radius);
|
||||||
|
font-size: .85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.themes-gallery__chip--accent {
|
||||||
|
background: var(--bonsai-accent);
|
||||||
|
color: var(--bonsai-bg);
|
||||||
|
border-color: var(--bonsai-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.link, .link:hover { transition: none; transform: none; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Loaded only when params.themeToggle = true.
|
||||||
|
// Persists user preference and overrides system color-scheme.
|
||||||
|
(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;
|
||||||
|
|
||||||
|
function syncPressed() {
|
||||||
|
const isDark = root.dataset.theme === 'dark'
|
||||||
|
|| (root.dataset.theme === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
|
btn.setAttribute('aria-pressed', String(isDark));
|
||||||
|
}
|
||||||
|
|
||||||
|
syncPressed();
|
||||||
|
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
||||||
|
root.dataset.theme = next;
|
||||||
|
localStorage.setItem(KEY, next);
|
||||||
|
syncPressed();
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -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://tiennm99.github.io/bonsai/"
|
||||||
|
tags = ["bio", "linktree", "minimal", "japanese", "personal", "landing", "icons"]
|
||||||
|
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]
|
||||||