From 2b0fba7acb460aa03c12208a363fa4c419c0c0b1 Mon Sep 17 00:00:00 2001 From: Helge Klein Date: Mon, 6 Apr 2026 18:22:40 +0200 Subject: [PATCH 1/9] Website planning --- .gitignore | 3 + .../website/Hugo_migration_plan.md | 721 ++++++++++++++++++ documentation_planning/website/Readme.md | 1 + 3 files changed, 725 insertions(+) create mode 100644 documentation_planning/website/Hugo_migration_plan.md diff --git a/.gitignore b/.gitignore index 5339deb..6f38886 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,6 @@ squashfs-root/ # Build timestamp (generated during Docker build) BUILD_TIME GIT_COMMIT + +# Temporary website source +website-temp/ diff --git a/documentation_planning/website/Hugo_migration_plan.md b/documentation_planning/website/Hugo_migration_plan.md new file mode 100644 index 0000000..2a6a66b --- /dev/null +++ b/documentation_planning/website/Hugo_migration_plan.md @@ -0,0 +1,721 @@ +# Hugo Migration Plan For Sambee Website + +## Goal + +Create a new Hugo site for Sambee inside this repository, using the copied `website-temp` site only as a source for reusable theme and build assets. + +The primary constraint is to migrate the visual system and generic site mechanics cleanly, without dragging over helgeklein.com-specific content structure, branding, menus, SEO settings, deployment assumptions, or generated artifacts. + +## Executive Recommendation + +Do not evolve `website-temp` in place. + +Instead: + +1. Create a brand-new site in `website/` +2. Extract the reusable presentation layer from `website-temp` +3. Rename the migrated custom theme to something Sambee-specific such as `sambee` +4. Recreate Sambee configuration, menus, and content structure from the planning docs in this repository +5. Add build, validation, and deployment pieces only after the site renders cleanly + +This is the lowest-risk path because `website-temp` is a complete production site, not a standalone theme package. + +## Confirmed Decisions + +The following decisions are now fixed and should be treated as migration requirements, not open questions: + +- Production domain: `https://sambee.net/` +- Search: keep Pagefind +- Comments: do not use Giscus or any other commenting system in the initial site +- PWA: not needed +- Docs hierarchy must exist as top-level sections under `docs/`: + - version-first storage directly below `docs/` + - audience-specific subsections within each version +- Docs must support multiple product versions, with content stored separately per version and a version switcher that lets visitors move between current and older documentation sets +- Version slugs should not use a `v` prefix; use `1.0`, `1.1`, and similar forms + +## Analysis Of `website-temp` + +### What It Is + +`website-temp` is a full Hugo website with a custom theme plus site-specific content, configuration, operational tooling, and generated outputs. + +The copied site is not organized as a clean reusable theme repository. Reusable theme logic exists, but it is split across: + +- site root files +- `themes/helgeklein` +- root-level Hugo config +- root-level `assets` +- root-level `static` +- root-level scripts + +### Core Build Stack + +The copied site currently depends on: + +- Hugo Extended, minimum version `0.151.0` +- Hugo modules via `go.mod` +- Node-based asset tooling via `package.json` +- Tailwind CSS 4 +- `tailwind-bootstrap-grid` +- `pagefind` for search +- a custom theme token generator script at `scripts/themeGenerator.js` + +### Reusable Theme Mechanics Already Present + +The following parts are good candidates for migration because they are structural or stylistic rather than brand-specific: + +- Theme layouts in `themes/helgeklein/layouts` +- Theme partials in `themes/helgeklein/layouts/_partials` +- Theme assets in `themes/helgeklein/assets` +- Theme translations in `themes/helgeklein/i18n` +- Theme token model in `data/theme.json` +- Theme CSS generation script in `scripts/themeGenerator.js` +- Generic site shell behavior: + - header and footer framework + - dark mode handling + - Tailwind-based styling + - page shell and asset pipeline + - table of contents support + - syntax highlighting support + - responsive article layout + - generic page templates + +### Root-Level Site Assumptions That Must Not Be Carried Over As-Is + +The following are clearly helgeklein.com-specific and should be recreated for Sambee rather than copied verbatim: + +- `baseURL = "https://helgeklein.com/"` +- `title = "Helge Klein"` +- `theme = "helgeklein"` +- blog-centric permalink rules for `content/blog` and `content/pages` +- blog taxonomies: categories and tags +- homepage implementation that renders latest blog posts +- three-level navigation menu tailored to tools, projects, and HAUS21 +- metadata author and description values +- social profile links +- Giscus repository IDs and comment settings +- Cloudflare R2 / CDN settings in `params.toml` +- footer legal links and imprint/privacy page assumptions +- helgeklein logo assets and brand typography choices +- PWA assumptions + +### Content Model In The Copied Site + +The content tree is blog-first: + +- `content/blog//...` +- `content/pages/...` + +The homepage template renders a post grid. Taxonomy and related-post templates are also blog-driven. This is a poor default for Sambee, whose planned information architecture is: + +- homepage +- end-user docs +- admin docs +- developer docs + +That means the Sambee website should be docs-first or product-docs-first, not blog-first. + +It also means the docs information architecture must be designed around versioned documentation from the start. Retrofitting version support after content migration would force URL, menu, search, and template changes across the whole docs tree. + +### Site-Specific Operational And Generated Baggage + +These folders or files are not migration inputs and should not be copied into the final site: + +- `.git/` +- `.github/` +- `.githooks/` +- `.devcontainer/` +- `.vscode/` +- `node_modules/` +- `public/` +- `resources/` +- `tmp/` +- `.pytest_cache/` +- `.hugo_build.lock` +- `hugo_stats.json` +- `content-editor.code-workspace` +- `migration/` +- `R2_SETUP.md` + +### Test And Validation Extras In The Copied Site + +The copied site also includes validation and automation that may be useful later, but should not block initial migration: + +- `.htmltest.yml` +- `tests/test_menu_urls.py` + +These are candidates for phase-two adoption after the new site structure is stable. + +## Recommended Target Structure + +Create a clean Hugo site at `website/` with this shape: + +```text +website/ +|-- archetypes/ +|-- assets/ +|-- config/ +| `-- _default/ +|-- content/ +| |-- _index.md +| `-- docs/ +| |-- _index.md +| |-- 1.0/ +| | |-- _index.md +| | |-- end-user/ +| | | `-- _index.md +| | |-- admin/ +| | | `-- _index.md +| | `-- developer/ +| | `-- _index.md +| `-- 1.1/ +| |-- _index.md +| |-- end-user/ +| | `-- _index.md +| |-- admin/ +| | `-- _index.md +| `-- developer/ +| `-- _index.md +|-- data/ +| `-- docs-versions.toml +|-- layouts/ +|-- static/ +|-- themes/ +| `-- sambee/ +| |-- assets/ +| |-- i18n/ +| `-- layouts/ +|-- scripts/ +|-- hugo.toml +|-- go.mod +|-- go.sum +|-- package.json +`-- package-lock.json +``` + +Notes: + +- Use `themes/sambee` rather than `themes/helgeklein` to avoid carrying old branding into the new site. +- Keep root-level `layouts/` initially empty unless Sambee needs site-specific overrides. +- Keep root-level `assets/` only for Sambee branding or site-only assets. Move generic styling into the theme. +- Use `content/docs/...` rather than `content/blog/...` and `content/pages/...` as the main long-term structure. +- Use version-first storage beneath `docs/`, with audience subsections inside each version. +- Preserve the requested docs hierarchy within each version: `end-user`, `admin`, and `developer`. +- Do not duplicate a separate `current` content tree. Instead, store real versions once and map unversioned landing URLs to the configured current version. +- Use plain version slugs such as `1.0` and `1.1` in folders and URLs, not `v1.0` or `v1.1`. +- Treat unversioned audience routes such as `/docs/end-user/` as convenience entry points to the configured current version, not as canonical content locations. + +## Migration Principles + +### Principle 1: Extract, Do Not Clone + +The new site should be built from a curated extraction of reusable pieces. Treat `website-temp` as a donor, not as the baseline working directory. + +### Principle 2: Favor A Small Site Root + +Anything reusable across pages should live in the theme. The site root should mostly contain: + +- Sambee config +- Sambee content +- Sambee branding +- Sambee-specific layout overrides + +### Principle 3: Remove Blog Coupling Early + +The biggest architectural mismatch is not the styling. It is the content model. Remove blog assumptions before building out Sambee pages, otherwise the site structure will keep fighting the docs information architecture. + +### Principle 4: Defer Nice-To-Haves + +Do not carry over every feature from the donor site on day one. Only the features already confirmed for Sambee should be treated as mandatory. + +This principle now applies only to unresolved features. Pagefind is required, while comments and PWA support are explicitly out of scope for the initial site. + +### Principle 5: Design For Docs Versioning From Day One + +Versioning must be part of the foundational content model, navigation model, and template model. + +That means: + +- version must be encoded in the docs content tree +- search results must be version-aware +- menus and breadcrumbs must know the selected version +- the docs version switcher must have a deterministic way to find the equivalent page in another version or fall back to that version's section landing page +- canonical docs URLs should be versioned + +## What To Keep, Adapt, Or Drop + +### Keep With Minimal Change + +- `themes/helgeklein/assets/` as source input for the new `themes/sambee/assets/` +- `themes/helgeklein/layouts/baseof.html` +- `themes/helgeklein/layouts/_partials/essentials/*` +- `themes/helgeklein/layouts/_partials/components/*` where components are generic +- `themes/helgeklein/layouts/_partials/icons/*` +- `themes/helgeklein/layouts/_markup/render-image.html` +- `themes/helgeklein/i18n/en.toml` +- `scripts/themeGenerator.js` +- `data/theme.json` as an initial design-token source +- Pagefind integration and related search UI assets + +### Keep But Rename Or Rewrite + +- `theme = "helgeklein"` to `theme = "sambee"` +- logo partial behavior: keep the fallback pattern, replace actual brand assets +- `data/theme.json`: keep the mechanism, replace colors and fonts +- root `assets/images/logo-*.svg`: replace with Sambee logos or text fallback +- `config/_default/module.toml`: keep only modules actually used by Sambee +- `package.json` scripts: keep the asset build ideas, simplify aggressively + +### Copy Only After Review + +- `.htmltest.yml` +- `tests/test_menu_urls.py` +- plugin JS list in `hugo.toml` + +### Keep As Explicit Product Decisions + +- Pagefind build and UI integration +- docs table of contents support +- version-aware docs navigation and switcher support + +### Drop Entirely From Initial Migration + +- all blog content +- all helgeklein menus +- categories and tags unless Sambee truly needs them +- related-post templates +- Giscus configuration +- comments partials and comment-related front matter behavior +- CDN / R2 configuration +- PWA module integration and `manifest.webmanifest` +- WordPress-compatible RSS customization +- site-specific redirect rules +- migration utilities from the blog site +- content editor workspace files + +## Detailed Migration Plan + +### Phase 1: Create A Clean Website Skeleton + +Goal: establish a fresh Hugo site that renders a minimal Sambee homepage with no dependency on the old content structure. + +Steps: + +1. Create `website/` as a new Hugo site root. +2. Add a minimal `hugo.toml` with: + - Sambee title + - production `baseURL = "https://sambee.net/"` + - `theme = "sambee"` + - `defaultContentLanguage = "en"` + - only the output formats and taxonomies actually needed +3. Add `config/_default/params.toml` with only Sambee-specific placeholders. +4. Create the top-level docs section tree: + - `content/docs/_index.md` + - `content/docs/1.0/_index.md` + - `content/docs/1.0/end-user/_index.md` + - `content/docs/1.0/admin/_index.md` + - `content/docs/1.0/developer/_index.md` +5. Add a temporary homepage content file and minimal docs section indexes. +6. Do not copy any content from `website-temp/content/`. + +Exit criteria: + +- `website/` builds successfully with a placeholder theme or a minimal copied shell. + +### Phase 2: Extract The Theme Into `themes/sambee` + +Goal: move the reusable visual and structural layer into a Sambee-specific custom theme. + +Steps: + +1. Create `website/themes/sambee/`. +2. Copy over these source directories from `website-temp/themes/helgeklein/`: + - `assets/` + - `i18n/` + - `layouts/` +3. Rename any obvious brand references from Helge Klein to Sambee where they are structural, not content. +4. Keep `baseof.html`, essentials partials, icons, and image render hooks. +5. Remove or quarantine obviously blog-specific templates from the theme immediately: + - `layouts/home.html` + - `layouts/blog/` + - `layouts/taxonomy.html` + - `layouts/term.html` + - homepage-specific post grid partials + - comments partials and comment invocations +6. Keep `layouts/pages/single.html` only if you still want article-style content pages. +7. Keep or adapt the existing search modal and search-related shell pieces because Pagefind is required. +8. Replace the homepage template with one designed for Sambee product content. + +Exit criteria: + +- The theme renders a generic page shell without relying on blog collections. + +### Phase 3: Migrate Asset Pipeline And Theme Tokens + +Goal: preserve the useful styling system without carrying over unnecessary toolchain complexity. + +Steps: + +1. Copy `scripts/themeGenerator.js` into `website/scripts/`. +2. Copy `data/theme.json` into `website/data/`. +3. Copy only the required asset entry points into `website/themes/sambee/assets/`. +4. Create a reduced `package.json` that keeps only dependencies actually required for: + - Tailwind CSS + - concurrent dev workflow, if still useful + - Pagefind + - Prettier, only if you want formatting inside `website/` +5. Remove unrelated package scripts such as: + - `dev:example` + - `build:example` + - `preview:example` + - `remove-darkmode` + - `remove-multilang` + - `project-setup` + - `theme-setup` + - `update-theme` +6. Keep a minimal script set: + - `dev` + - `build` + - `preview` + - `build:search` +7. Generate fresh `generated-theme.css` for the new site. +8. Ensure the build pipeline runs Pagefind against the built `website/public` output. + +Exit criteria: + +- Theme CSS is generated successfully and the site renders with Sambee branding tokens. +- Pagefind index generation is wired into normal build output. + +### Phase 4: Rebuild Sambee Configuration From Scratch + +Goal: replace helgeklein.com settings with Sambee-specific site config. + +Steps: + +1. Recreate `hugo.toml` instead of editing the copied file line-by-line. +2. Recreate `config/_default/params.toml` with Sambee placeholders only. +3. Recreate `config/_default/menus.en.toml` from the Sambee information architecture. +4. Recreate `config/_default/languages.toml` only as needed. +5. Recreate `config/_default/module.toml` and keep only the Hugo modules Sambee actually uses. +6. Set the production domain and canonical URL behavior from the start. + +Recommended initial module set: + +- `basic-seo` +- `render-link` +- `table-of-contents` + +Optional module set: + +- `videos` +- `modal` +- `site-verifications` +- `button` + +Likely removals at first pass: + +- PWA module +- modal module if search or other modal UI is not yet used + +Required configuration decisions: + +- `baseURL = "https://sambee.net/"` +- no Giscus configuration +- no PWA output format or manifest wiring +- search configuration for Pagefind-enabled templates and assets + +Exit criteria: + +- No Helge Klein URLs, names, IDs, social links, or repo IDs remain in active site config. +- Active configuration matches the fixed Sambee product decisions. + +### Phase 5: Recreate The Content Architecture For Sambee + +Goal: align content with the planning already done in this repository. + +Steps: + +1. Build the homepage around Sambee messaging from the homepage planning docs. +2. Create docs landing pages for: + - end-user docs + - admin docs + - developer docs +3. Establish a content organization that maps to the planning documents, not the blog taxonomy. +4. Use this docs section layout: + - `content/docs//end-user/...` + - `content/docs//admin/...` + - `content/docs//developer/...` +5. Create a small set of archetypes for consistent front matter. +6. Add unversioned audience landing pages that explain audience scope and link to the current version. +7. Only after the information architecture is stable, begin migrating material from: + - `documentation/` + - `documentation_developers/` + +Exit criteria: + +- Navigation, section landing pages, and URL structure match Sambee plans rather than blog-era conventions. + +### Phase 6: Implement Versioned Docs Architecture + +Goal: make versioning a first-class part of the docs subsite rather than a later add-on. + +Recommended approach: use path-based versioning directly below `docs/`, with audience subsections inside each version. + +Recommended content layout: + +- `content/docs/1.0/end-user/...` +- `content/docs/1.0/admin/...` +- `content/docs/1.0/developer/...` +- `content/docs/1.1/end-user/...` +- `content/docs/1.1/admin/...` +- `content/docs/1.1/developer/...` + +Recommended support files: + +- `data/docs-versions.toml` to declare: + - current version + - all supported versions + - labels such as `current`, `LTS`, or `unsupported` + - optional per-version release metadata + +Recommended front matter fields on docs pages: + +- `doc_id`: stable identifier shared across versions of the same conceptual page +- `product_version`: explicit version string such as `1.1` +- `audience`: one of `end-user`, `admin`, or `developer` +- optional `version_aliases` or `legacy_paths` when old URLs must redirect + +Versioning rules: + +1. The same conceptual document must keep the same `doc_id` across versions. +2. Relative slug structure should stay aligned across versions whenever possible. +3. Canonical docs page URLs should be versioned, for example `/docs/1.1/end-user/...`. +4. Unversioned audience landing pages such as `/docs/end-user/` should point to the configured current version landing page, not duplicate content. +5. Individual docs pages should not exist at both unversioned and versioned URLs unless an intentional alias is needed. +6. Version folder names and URL slugs should use plain release identifiers such as `1.0` and `1.1`, not `v1.0` and `v1.1`. + +Version switcher behavior: + +1. On a versioned docs page, show a version switcher containing the current version and older supported versions. +2. When a visitor selects another version, resolve the target page by matching: + - same `audience` + - same `doc_id` + - selected `product_version` +3. If no equivalent page exists in the selected version, send the visitor to that version's audience landing page and show a short notice that the exact page is unavailable in that version. +4. The switcher should be absent or disabled outside the docs subsite. + +Navigation and breadcrumb requirements: + +- docs navigation must indicate both audience and selected version +- breadcrumbs should include the version level where helpful +- section sidebars should be generated per version and audience, not globally across all versions + +Search requirements: + +- Pagefind results must include enough metadata to identify version and audience +- docs search UI should default to the selected version when the visitor is inside versioned docs +- optionally allow widening the search scope to all versions if that proves useful later + +Implementation note: + +The simplest robust approach is to build the version switcher on top of stable page metadata rather than path guessing. In practice, that means using `doc_id` and `product_version` in front matter and resolving cross-version matches from Hugo page collections. + +Exit criteria: + +- docs content exists in separate versioned trees +- the current version is centrally configurable +- cross-version page switching works or falls back predictably +- search can distinguish versions +- canonical docs URLs are versioned + +### Phase 7: Implement Required Search And Exclude Unneeded Features + +Goal: retain the required search experience while explicitly removing features that are out of scope. + +Required feature work: + +1. Search + - Keep Pagefind and integrate it into the docs experience. + - Ensure search works across versioned docs. + - Decide whether the default scope should be current version only or current section plus current version. +2. Taxonomies + - Avoid categories and tags unless the site includes a real blog. +3. RSS + - Keep only if news or blog content exists. +4. Related content widgets + - Use only if there is enough long-form editorial content. + +Explicit exclusions: + +- no Giscus +- no other page-level commenting system +- no PWA manifest or install flow +- no PWA Hugo module + +Exit criteria: + +- Pagefind works in the built site. +- Comments and PWA functionality are absent from the active implementation. + +### Phase 8: Add Validation, CI, And Deployment + +Goal: harden the new site after the architecture and theme are stable. + +Steps: + +1. Add HTML validation only after routes are settled. +2. Port `.htmltest.yml` only if the generated site structure is similar enough. +3. Either rewrite or replace `tests/test_menu_urls.py` for the new menu model. +4. Add build commands to repository tooling. +5. Add deployment automation for `sambee.net` once the final hosting target is chosen. +6. Add checks that specifically validate versioned docs links and version switcher targets. +7. Add checks that validate the unversioned audience entry points resolve to the configured current version. + +Exit criteria: + +- Site builds reproducibly and validation checks match the new site, not the old one. +- Production deployment assumptions are aligned with `sambee.net`. + +## Concrete File Migration Map + +### Files To Copy Early + +Copy these early into the new `website/` tree for extraction work: + +- `website-temp/themes/helgeklein/assets/**` -> `website/themes/sambee/assets/**` +- `website-temp/themes/helgeklein/i18n/**` -> `website/themes/sambee/i18n/**` +- `website-temp/themes/helgeklein/layouts/**` -> `website/themes/sambee/layouts/**` +- `website-temp/data/theme.json` -> `website/data/theme.json` +- `website-temp/scripts/themeGenerator.js` -> `website/scripts/themeGenerator.js` +- Pagefind-related assets and templates that are actually used by the migrated theme + +### Files To Recreate Manually + +- `website/hugo.toml` +- `website/config/_default/params.toml` +- `website/config/_default/menus.en.toml` +- `website/config/_default/module.toml` +- `website/data/docs-versions.toml` +- `website/content/**` + +### Files To Copy Only If Needed Later + +- `website-temp/package.json` +- `website-temp/package-lock.json` +- `website-temp/go.mod` +- `website-temp/go.sum` +- `website-temp/.htmltest.yml` +- `website-temp/tests/test_menu_urls.py` + +### Files To Ignore Completely + +- `website-temp/content/**` +- `website-temp/public/**` +- `website-temp/resources/**` +- `website-temp/node_modules/**` +- `website-temp/migration/**` +- `website-temp/tmp/**` +- `website-temp/.git/**` +- `website-temp/static/manifest.webmanifest` + +## Risks And How To Avoid Them + +### Risk: Hidden Blog Assumptions In Shared Partials + +Some partials look generic but still depend on blog-style fields, featured images, or post collections. + +Mitigation: + +- smoke-test every copied template against a minimal Sambee page before trusting it +- remove homepage and taxonomy templates first +- keep site root overrides available to replace theme templates quickly + +### Risk: Versioned Docs Become Inconsistent Across Releases + +Without a strong convention, versioned docs can drift in filenames, slugs, or page identity, making the version switcher unreliable. + +Mitigation: + +- require a stable `doc_id` on versioned docs pages +- keep per-version folder structures aligned wherever possible +- add automated checks for cross-version page resolution + +### Risk: Search Quality Degrades When Multiple Versions Are Indexed + +If all versions are indexed without metadata and scoping, visitors may get noisy or outdated results. + +Mitigation: + +- index version and audience metadata with each docs page +- default docs search to the selected version context +- clearly label search results with version badges + +### Risk: Carrying Over Too Many Optional Dependencies + +The copied site has Hugo modules, Tailwind, search tooling, and validation extras. + +Mitigation: + +- start with the smallest working set +- add modules and tooling back one feature at a time + +### Risk: Theme And Site Responsibilities Remain Blurred + +If too much logic stays at the site root, future reuse and maintenance become harder. + +Mitigation: + +- move generic presentation into `themes/sambee` +- keep Sambee content and configuration at the site root + +### Risk: Generated Files Accidentally Enter Source Control + +The copied site contains several generated outputs. + +Mitigation: + +- define ignores for `website/public/`, `website/resources/`, and generated CSS artifacts where appropriate +- generate fresh outputs in the new site instead of copying them + +## Suggested Order Of Implementation Work + +1. Create `website/` skeleton +2. Create `themes/sambee/` +3. Copy theme assets, layouts, i18n, `theme.json`, and theme generator script +4. Strip blog-specific templates from the copied theme +5. Recreate minimal Hugo config for Sambee +6. Replace logos, colors, and typography tokens +7. Build a minimal homepage and one docs page +8. Implement versioned docs structure and version switcher support +9. Wire Pagefind into version-aware docs search +10. Add validation and deployment +11. Migrate actual documentation content + +## Definition Of Done For The Migration + +The migration should be considered structurally complete when all of the following are true: + +1. `website-temp` is no longer part of the active implementation path +2. `website/` builds without depending on blog content +3. no Helge Klein branding or IDs remain in active Sambee site files +4. the active theme is `sambee` +5. homepage and docs navigation match the Sambee planning docs +6. docs content is organized under `docs//end-user`, `docs//admin`, and `docs//developer` +7. versioned docs are stored separately per product version and can be switched predictably +8. Pagefind works for the active site +9. Giscus and PWA functionality are absent from the active site + +## Recommended Next Step + +The next implementation step should be to scaffold `website/` and extract only the minimum viable theme layer: + +- theme shell +- asset pipeline +- Sambee config +- placeholder homepage +- placeholder docs landing page +- versioned docs scaffolding for one initial product version +- Pagefind-enabled docs search shell + +After that, the remaining migration decisions become concrete and testable rather than theoretical. diff --git a/documentation_planning/website/Readme.md b/documentation_planning/website/Readme.md index dcdf4d7..34551fa 100644 --- a/documentation_planning/website/Readme.md +++ b/documentation_planning/website/Readme.md @@ -23,6 +23,7 @@ The target structure is: - [Homepage_text_copy.md](./Homepage_text_copy.md): draft homepage copy derived from the homepage plan - [EndUserDocs_planning.md](./EndUserDocs_planning.md): task-oriented end-user docs structure, terminology, and coverage planning - [AdminDocs_planning.md](./AdminDocs_planning.md): administrator-focused deployment, configuration, operations, and support planning +- [Hugo_migration_plan.md](./Hugo_migration_plan.md): analysis of `website-temp` and a phased plan to build a clean Sambee Hugo site while migrating only the reusable parts of the helgeklein.com theme Developer docs are part of the target site structure, but they do not yet have a dedicated planning file in this folder. From 5db8f890fa6efeaf38868b3d6f78cfa92027c2c9 Mon Sep 17 00:00:00 2001 From: Helge Klein Date: Mon, 6 Apr 2026 19:02:32 +0200 Subject: [PATCH 2/9] Hugo migration plan --- .../website/Hugo_migration_plan.md | 896 +++++++----------- 1 file changed, 346 insertions(+), 550 deletions(-) diff --git a/documentation_planning/website/Hugo_migration_plan.md b/documentation_planning/website/Hugo_migration_plan.md index 2a6a66b..f284903 100644 --- a/documentation_planning/website/Hugo_migration_plan.md +++ b/documentation_planning/website/Hugo_migration_plan.md @@ -2,154 +2,71 @@ ## Goal -Create a new Hugo site for Sambee inside this repository, using the copied `website-temp` site only as a source for reusable theme and build assets. +Build a new Hugo site for Sambee inside this repository, using `website-temp` only as a donor for reusable theme and build pieces. -The primary constraint is to migrate the visual system and generic site mechanics cleanly, without dragging over helgeklein.com-specific content structure, branding, menus, SEO settings, deployment assumptions, or generated artifacts. +The migration should produce a clean `website/` directory with: -## Executive Recommendation +- a Sambee-specific custom theme +- versioned docs with plain version slugs such as `1.0` and `1.1` +- Pagefind search +- no comments system +- no PWA support +- no dependency on helgeklein.com content, branding, or deployment assumptions -Do not evolve `website-temp` in place. +## Fixed Decisions -Instead: - -1. Create a brand-new site in `website/` -2. Extract the reusable presentation layer from `website-temp` -3. Rename the migrated custom theme to something Sambee-specific such as `sambee` -4. Recreate Sambee configuration, menus, and content structure from the planning docs in this repository -5. Add build, validation, and deployment pieces only after the site renders cleanly - -This is the lowest-risk path because `website-temp` is a complete production site, not a standalone theme package. - -## Confirmed Decisions - -The following decisions are now fixed and should be treated as migration requirements, not open questions: +These are no longer open questions. - Production domain: `https://sambee.net/` +- Site generator: Hugo with a custom theme +- Theme source: extracted from `website-temp`, then renamed and cleaned up - Search: keep Pagefind -- Comments: do not use Giscus or any other commenting system in the initial site -- PWA: not needed -- Docs hierarchy must exist as top-level sections under `docs/`: - - version-first storage directly below `docs/` - - audience-specific subsections within each version -- Docs must support multiple product versions, with content stored separately per version and a version switcher that lets visitors move between current and older documentation sets -- Version slugs should not use a `v` prefix; use `1.0`, `1.1`, and similar forms - -## Analysis Of `website-temp` - -### What It Is - -`website-temp` is a full Hugo website with a custom theme plus site-specific content, configuration, operational tooling, and generated outputs. - -The copied site is not organized as a clean reusable theme repository. Reusable theme logic exists, but it is split across: - -- site root files -- `themes/helgeklein` -- root-level Hugo config -- root-level `assets` -- root-level `static` -- root-level scripts - -### Core Build Stack - -The copied site currently depends on: - -- Hugo Extended, minimum version `0.151.0` -- Hugo modules via `go.mod` -- Node-based asset tooling via `package.json` -- Tailwind CSS 4 -- `tailwind-bootstrap-grid` -- `pagefind` for search -- a custom theme token generator script at `scripts/themeGenerator.js` - -### Reusable Theme Mechanics Already Present - -The following parts are good candidates for migration because they are structural or stylistic rather than brand-specific: - -- Theme layouts in `themes/helgeklein/layouts` -- Theme partials in `themes/helgeklein/layouts/_partials` -- Theme assets in `themes/helgeklein/assets` -- Theme translations in `themes/helgeklein/i18n` -- Theme token model in `data/theme.json` -- Theme CSS generation script in `scripts/themeGenerator.js` -- Generic site shell behavior: - - header and footer framework - - dark mode handling - - Tailwind-based styling - - page shell and asset pipeline - - table of contents support - - syntax highlighting support - - responsive article layout - - generic page templates - -### Root-Level Site Assumptions That Must Not Be Carried Over As-Is - -The following are clearly helgeklein.com-specific and should be recreated for Sambee rather than copied verbatim: - -- `baseURL = "https://helgeklein.com/"` -- `title = "Helge Klein"` -- `theme = "helgeklein"` -- blog-centric permalink rules for `content/blog` and `content/pages` -- blog taxonomies: categories and tags -- homepage implementation that renders latest blog posts -- three-level navigation menu tailored to tools, projects, and HAUS21 -- metadata author and description values -- social profile links -- Giscus repository IDs and comment settings -- Cloudflare R2 / CDN settings in `params.toml` -- footer legal links and imprint/privacy page assumptions -- helgeklein logo assets and brand typography choices -- PWA assumptions - -### Content Model In The Copied Site - -The content tree is blog-first: - -- `content/blog//...` -- `content/pages/...` - -The homepage template renders a post grid. Taxonomy and related-post templates are also blog-driven. This is a poor default for Sambee, whose planned information architecture is: - -- homepage -- end-user docs -- admin docs -- developer docs - -That means the Sambee website should be docs-first or product-docs-first, not blog-first. - -It also means the docs information architecture must be designed around versioned documentation from the start. Retrofitting version support after content migration would force URL, menu, search, and template changes across the whole docs tree. - -### Site-Specific Operational And Generated Baggage - -These folders or files are not migration inputs and should not be copied into the final site: - -- `.git/` -- `.github/` -- `.githooks/` -- `.devcontainer/` -- `.vscode/` -- `node_modules/` -- `public/` -- `resources/` -- `tmp/` -- `.pytest_cache/` -- `.hugo_build.lock` -- `hugo_stats.json` -- `content-editor.code-workspace` -- `migration/` -- `R2_SETUP.md` - -### Test And Validation Extras In The Copied Site - -The copied site also includes validation and automation that may be useful later, but should not block initial migration: +- Comments: none +- PWA: none +- Docs storage model: version-first under `docs/` +- Version slug format: `1.0`, `1.1`, and similar; no `v` prefix +- Initial scaffolded version set: `1.0` only +- Current version at scaffold start: `1.0` +- Unversioned docs book routes use hard redirects to the current version +- Search default scope inside docs: current version only +- All docs books share one template initially +- Start with the smallest practical Hugo module and tooling set +- Deployment model should broadly mirror the source site approach +- Docs books within each version: + - `end-user` + - `admin` + - `developer` + +## Donor Site Assessment + +`website-temp` is a complete production Hugo website, not a reusable theme package. + +What is useful: + +- `themes/helgeklein/layouts` +- `themes/helgeklein/layouts/_partials` +- `themes/helgeklein/assets` +- `themes/helgeklein/i18n` +- `data/theme.json` +- `scripts/themeGenerator.js` +- selected search UI and Pagefind-related assets -- `.htmltest.yml` -- `tests/test_menu_urls.py` +What is donor-specific and must not be carried over as active implementation: + +- helgeklein.com branding, title, metadata, menus, legal/footer assumptions, and social links +- Giscus configuration +- PWA wiring and manifest +- blog-centric content model and templates +- generated outputs and local tooling state such as `public`, `resources`, `node_modules`, `.hugo_build.lock`, and `hugo_stats.json` +- deployment or storage assumptions tied to Cloudflare R2 or the original site + +The critical mismatch is structural, not stylistic: the donor site is blog-first, while Sambee needs a docs-first product site with explicit documentation versioning. -These are candidates for phase-two adoption after the new site structure is stable. +## Target Architecture -## Recommended Target Structure +### Directory Layout -Create a clean Hugo site at `website/` with this shape: +The new site should live in `website/` with this structure: ```text website/ @@ -169,14 +86,6 @@ website/ | | | `-- _index.md | | `-- developer/ | | `-- _index.md -| `-- 1.1/ -| |-- _index.md -| |-- end-user/ -| | `-- _index.md -| |-- admin/ -| | `-- _index.md -| `-- developer/ -| `-- _index.md |-- data/ | `-- docs-versions.toml |-- layouts/ @@ -194,528 +103,415 @@ website/ `-- package-lock.json ``` -Notes: +### Routing Model + +Canonical docs URLs must be versioned. -- Use `themes/sambee` rather than `themes/helgeklein` to avoid carrying old branding into the new site. -- Keep root-level `layouts/` initially empty unless Sambee needs site-specific overrides. -- Keep root-level `assets/` only for Sambee branding or site-only assets. Move generic styling into the theme. -- Use `content/docs/...` rather than `content/blog/...` and `content/pages/...` as the main long-term structure. -- Use version-first storage beneath `docs/`, with audience subsections inside each version. -- Preserve the requested docs hierarchy within each version: `end-user`, `admin`, and `developer`. -- Do not duplicate a separate `current` content tree. Instead, store real versions once and map unversioned landing URLs to the configured current version. -- Use plain version slugs such as `1.0` and `1.1` in folders and URLs, not `v1.0` or `v1.1`. -- Treat unversioned audience routes such as `/docs/end-user/` as convenience entry points to the configured current version, not as canonical content locations. +Examples: -## Migration Principles +- `/docs/1.0/end-user/` +- `/docs/1.0/end-user/install/` +- `/docs/1.0/admin/configuration/` +- `/docs/1.0/developer/architecture/` -### Principle 1: Extract, Do Not Clone +Unversioned docs book routes are convenience entry points, not canonical content locations. -The new site should be built from a curated extraction of reusable pieces. Treat `website-temp` as a donor, not as the baseline working directory. +Examples: -### Principle 2: Favor A Small Site Root +- `/docs/end-user/` -> hard redirect to `/docs/1.0/end-user/` +- `/docs/admin/` -> hard redirect to `/docs/1.0/admin/` +- `/docs/developer/` -> hard redirect to `/docs/1.0/developer/` -Anything reusable across pages should live in the theme. The site root should mostly contain: +There must not be duplicate full content trees at both versioned and unversioned URLs. -- Sambee config -- Sambee content -- Sambee branding -- Sambee-specific layout overrides +### Hugo Content Rules -### Principle 3: Remove Blog Coupling Early +- Use `_index.md` for the docs root, each version root, and each docs book root. +- Keep versioned docs pages under `content/docs///...`. +- Keep relative page structure aligned across versions whenever the same conceptual page exists in multiple releases. +- Use page bundles for pages with version-specific assets such as screenshots or downloadable files. -The biggest architectural mismatch is not the styling. It is the content model. Remove blog assumptions before building out Sambee pages, otherwise the site structure will keep fighting the docs information architecture. +### Version Metadata -### Principle 4: Defer Nice-To-Haves +`data/docs-versions.toml` should be the single source of truth for visible and supported versions. -Do not carry over every feature from the donor site on day one. Only the features already confirmed for Sambee should be treated as mandatory. +It should define at least: -This principle now applies only to unresolved features. Pagefind is required, while comments and PWA support are explicitly out of scope for the initial site. +- `current` +- ordered list of versions from newest to oldest +- display label per version +- support status per version, such as `current`, `supported`, `unsupported`, or `archived` +- optional release and end-of-support dates -### Principle 5: Design For Docs Versioning From Day One +Suggested status policy: -Versioning must be part of the foundational content model, navigation model, and template model. +- `current`: default target for unversioned docs routes, visible in switcher, indexed by search, no outdated-version warning +- `supported`: still maintained, visible in switcher, optionally indexed, warning banner shown when it is not the current version +- `unsupported`: published for reference, warning banner shown, excluded from default search scope +- `archived`: still reachable by direct link if retained, hidden from normal switcher and excluded from search -That means: +Recommended shape: -- version must be encoded in the docs content tree -- search results must be version-aware -- menus and breadcrumbs must know the selected version -- the docs version switcher must have a deterministic way to find the equivalent page in another version or fall back to that version's section landing page -- canonical docs URLs should be versioned +```toml +current = "1.0" -## What To Keep, Adapt, Or Drop +[[versions]] +slug = "1.0" +label = "1.0" +status = "current" +searchable = true +``` -### Keep With Minimal Change +### Navigation And Ordering Strategy -- `themes/helgeklein/assets/` as source input for the new `themes/sambee/assets/` -- `themes/helgeklein/layouts/baseof.html` -- `themes/helgeklein/layouts/_partials/essentials/*` -- `themes/helgeklein/layouts/_partials/components/*` where components are generic -- `themes/helgeklein/layouts/_partials/icons/*` -- `themes/helgeklein/layouts/_markup/render-image.html` -- `themes/helgeklein/i18n/en.toml` -- `scripts/themeGenerator.js` -- `data/theme.json` as an initial design-token source -- Pagefind integration and related search UI assets +Use an explicit ordered navigation definition as the source of truth for docs ordering. -### Keep But Rename Or Rewrite +Why this is the best fit: -- `theme = "helgeklein"` to `theme = "sambee"` -- logo partial behavior: keep the fallback pattern, replace actual brand assets -- `data/theme.json`: keep the mechanism, replace colors and fonts -- root `assets/images/logo-*.svg`: replace with Sambee logos or text fallback -- `config/_default/module.toml`: keep only modules actually used by Sambee -- `package.json` scripts: keep the asset build ideas, simplify aggressively +- Inferred ordering from the filesystem or page metadata is harder to audit once the docs grow. +- Docusaurus uses explicit sidebars when strict ordered reader flow matters. +- Hugo menu guidance recommends using one consistent definition method across the site rather than mixing approaches. +- Read the Docs emphasizes that readers need a clear, stable structure to navigate successfully. -### Copy Only After Review +Recommended rule set: -- `.htmltest.yml` -- `tests/test_menu_urls.py` -- plugin JS list in `hugo.toml` +- maintain one ordered navigation file per docs version +- each navigation file defines the exact order of books, sections, and pages +- page references in the navigation file should use stable `doc_id` values +- the filesystem groups content, but it is not the final source of sidebar ordering truth +- one shared docs template renders navigation for all books -### Keep As Explicit Product Decisions +Recommended navigation data layout: -- Pagefind build and UI integration -- docs table of contents support -- version-aware docs navigation and switcher support +```text +website/ +|-- data/ +| |-- docs-versions.toml +| `-- docs-nav/ +| `-- 1.0.toml +``` -### Drop Entirely From Initial Migration +Recommended `data/docs-nav/1.0.toml` shape: -- all blog content -- all helgeklein menus -- categories and tags unless Sambee truly needs them -- related-post templates -- Giscus configuration -- comments partials and comment-related front matter behavior -- CDN / R2 configuration -- PWA module integration and `manifest.webmanifest` -- WordPress-compatible RSS customization -- site-specific redirect rules -- migration utilities from the blog site -- content editor workspace files - -## Detailed Migration Plan - -### Phase 1: Create A Clean Website Skeleton - -Goal: establish a fresh Hugo site that renders a minimal Sambee homepage with no dependency on the old content structure. - -Steps: - -1. Create `website/` as a new Hugo site root. -2. Add a minimal `hugo.toml` with: - - Sambee title - - production `baseURL = "https://sambee.net/"` - - `theme = "sambee"` - - `defaultContentLanguage = "en"` - - only the output formats and taxonomies actually needed -3. Add `config/_default/params.toml` with only Sambee-specific placeholders. -4. Create the top-level docs section tree: - - `content/docs/_index.md` - - `content/docs/1.0/_index.md` - - `content/docs/1.0/end-user/_index.md` - - `content/docs/1.0/admin/_index.md` - - `content/docs/1.0/developer/_index.md` -5. Add a temporary homepage content file and minimal docs section indexes. -6. Do not copy any content from `website-temp/content/`. - -Exit criteria: - -- `website/` builds successfully with a placeholder theme or a minimal copied shell. - -### Phase 2: Extract The Theme Into `themes/sambee` - -Goal: move the reusable visual and structural layer into a Sambee-specific custom theme. - -Steps: - -1. Create `website/themes/sambee/`. -2. Copy over these source directories from `website-temp/themes/helgeklein/`: - - `assets/` - - `i18n/` - - `layouts/` -3. Rename any obvious brand references from Helge Klein to Sambee where they are structural, not content. -4. Keep `baseof.html`, essentials partials, icons, and image render hooks. -5. Remove or quarantine obviously blog-specific templates from the theme immediately: - - `layouts/home.html` - - `layouts/blog/` - - `layouts/taxonomy.html` - - `layouts/term.html` - - homepage-specific post grid partials - - comments partials and comment invocations -6. Keep `layouts/pages/single.html` only if you still want article-style content pages. -7. Keep or adapt the existing search modal and search-related shell pieces because Pagefind is required. -8. Replace the homepage template with one designed for Sambee product content. - -Exit criteria: - -- The theme renders a generic page shell without relying on blog collections. - -### Phase 3: Migrate Asset Pipeline And Theme Tokens - -Goal: preserve the useful styling system without carrying over unnecessary toolchain complexity. - -Steps: - -1. Copy `scripts/themeGenerator.js` into `website/scripts/`. -2. Copy `data/theme.json` into `website/data/`. -3. Copy only the required asset entry points into `website/themes/sambee/assets/`. -4. Create a reduced `package.json` that keeps only dependencies actually required for: - - Tailwind CSS - - concurrent dev workflow, if still useful - - Pagefind - - Prettier, only if you want formatting inside `website/` -5. Remove unrelated package scripts such as: - - `dev:example` - - `build:example` - - `preview:example` - - `remove-darkmode` - - `remove-multilang` - - `project-setup` - - `theme-setup` - - `update-theme` -6. Keep a minimal script set: - - `dev` - - `build` - - `preview` - - `build:search` -7. Generate fresh `generated-theme.css` for the new site. -8. Ensure the build pipeline runs Pagefind against the built `website/public` output. +```toml +[[books]] +slug = "end-user" +title = "End-User" +landing_doc_id = "end-user-index" + + [[books.sections]] + title = "Getting Started" + doc_ids = ["install", "first-login", "browse-files"] + + [[books.sections]] + title = "Editing" + doc_ids = ["edit-markdown", "open-in-desktop-app"] -Exit criteria: +[[books]] +slug = "admin" +title = "Admin" +landing_doc_id = "admin-index" +``` + +Operational rules: + +- every page visible in docs navigation must appear exactly once in the corresponding version navigation file +- order is determined first by the navigation file, not by folder name or title +- do not use `weight` for docs ordering in front matter or navigation data +- the navigation file should be validated in CI against the existing docs pages for that version + +This gives Sambee the explicit first, second, third ordering you asked for without making URL structure or titles carry the burden of navigation control. -- Theme CSS is generated successfully and the site renders with Sambee branding tokens. -- Pagefind index generation is wired into normal build output. +### Docs Page Front Matter Contract -### Phase 4: Rebuild Sambee Configuration From Scratch +Every versioned docs page should include: -Goal: replace helgeklein.com settings with Sambee-specific site config. +- `title` +- `doc_id`: stable across versions for the same conceptual page +- `product_version`: same value as the version folder, for example `1.0` +- `book`: one of `end-user`, `admin`, or `developer` -Steps: +Optional fields: -1. Recreate `hugo.toml` instead of editing the copied file line-by-line. -2. Recreate `config/_default/params.toml` with Sambee placeholders only. -3. Recreate `config/_default/menus.en.toml` from the Sambee information architecture. -4. Recreate `config/_default/languages.toml` only as needed. -5. Recreate `config/_default/module.toml` and keep only the Hugo modules Sambee actually uses. -6. Set the production domain and canonical URL behavior from the start. - -Recommended initial module set: - -- `basic-seo` -- `render-link` -- `table-of-contents` - -Optional module set: - -- `videos` -- `modal` -- `site-verifications` -- `button` +- `layout` +- `description` +- `version_aliases` +- `legacy_paths` -Likely removals at first pass: +Notes: + +- `doc_id` is not the same as a slug. A slug controls the URL path for one page version. `doc_id` identifies the conceptual document across versions so the switcher can find the equivalent page even if titles or slugs change. +- Docs ordering should come only from `data/docs-nav/.toml` so there is no second ordering system to reconcile later. -- PWA module -- modal module if search or other modal UI is not yet used +Example: -Required configuration decisions: +```toml ++++ +title = "Install Sambee" +doc_id = "install" +product_version = "1.0" +book = "end-user" ++++ +``` -- `baseURL = "https://sambee.net/"` -- no Giscus configuration -- no PWA output format or manifest wiring -- search configuration for Pagefind-enabled templates and assets +### Version Switcher Rules -Exit criteria: +- Show the version switcher only on docs pages. +- Populate the switcher from `data/docs-versions.toml`. +- Resolve the same page in another version by matching: + - `doc_id` + - `book` + - selected version slug +- If no equivalent page exists, send the user to that version’s book landing page. +- Mark outdated versions visibly in the UI. -- No Helge Klein URLs, names, IDs, social links, or repo IDs remain in active site config. -- Active configuration matches the fixed Sambee product decisions. +### Search Rules -### Phase 5: Recreate The Content Architecture For Sambee +Pagefind is mandatory. -Goal: align content with the planning already done in this repository. +Search must: -Steps: +- index docs version and book metadata +- default to the current page’s version only when the user is inside docs +- label results with version information +- exclude versions marked as not searchable in `data/docs-versions.toml` -1. Build the homepage around Sambee messaging from the homepage planning docs. -2. Create docs landing pages for: - - end-user docs - - admin docs - - developer docs -3. Establish a content organization that maps to the planning documents, not the blog taxonomy. -4. Use this docs section layout: - - `content/docs//end-user/...` - - `content/docs//admin/...` - - `content/docs//developer/...` -5. Create a small set of archetypes for consistent front matter. -6. Add unversioned audience landing pages that explain audience scope and link to the current version. -7. Only after the information architecture is stable, begin migrating material from: - - `documentation/` - - `documentation_developers/` +## Migration Scope -Exit criteria: +### Copy Early -- Navigation, section landing pages, and URL structure match Sambee plans rather than blog-era conventions. +- `website-temp/themes/helgeklein/assets/**` -> `website/themes/sambee/assets/**` +- `website-temp/themes/helgeklein/i18n/**` -> `website/themes/sambee/i18n/**` +- `website-temp/themes/helgeklein/layouts/**` -> `website/themes/sambee/layouts/**` +- `website-temp/data/theme.json` -> `website/data/theme.json` +- `website-temp/scripts/themeGenerator.js` -> `website/scripts/themeGenerator.js` +- Pagefind-related UI assets that are actually used after cleanup -### Phase 6: Implement Versioned Docs Architecture +### Recreate Manually -Goal: make versioning a first-class part of the docs subsite rather than a later add-on. +- `website/hugo.toml` +- `website/config/_default/params.toml` +- `website/config/_default/menus.en.toml` +- `website/config/_default/module.toml` +- `website/data/docs-versions.toml` +- `website/data/docs-nav/.toml` +- `website/content/**` -Recommended approach: use path-based versioning directly below `docs/`, with audience subsections inside each version. +### Defer Until Needed -Recommended content layout: +- `.htmltest.yml` +- `tests/test_menu_urls.py` +- optional Hugo modules such as `videos`, `site-verifications`, and `button` -- `content/docs/1.0/end-user/...` -- `content/docs/1.0/admin/...` -- `content/docs/1.0/developer/...` -- `content/docs/1.1/end-user/...` -- `content/docs/1.1/admin/...` -- `content/docs/1.1/developer/...` +### Do Not Carry Over -Recommended support files: +- donor site content +- donor menus and legal/footer assumptions +- blog templates and related-post behavior +- Giscus and comment-related partials +- PWA module integration and `manifest.webmanifest` +- donor deployment configuration +- generated output directories and lock files -- `data/docs-versions.toml` to declare: - - current version - - all supported versions - - labels such as `current`, `LTS`, or `unsupported` - - optional per-version release metadata +## Implementation Plan -Recommended front matter fields on docs pages: +### Phase 1: Scaffold A Clean Site -- `doc_id`: stable identifier shared across versions of the same conceptual page -- `product_version`: explicit version string such as `1.1` -- `audience`: one of `end-user`, `admin`, or `developer` -- optional `version_aliases` or `legacy_paths` when old URLs must redirect +Create `website/` and add: -Versioning rules: +- `hugo.toml` with `baseURL = "https://sambee.net/"`, `theme = "sambee"`, and only the needed outputs +- `config/_default/params.toml` +- `content/_index.md` +- `content/docs/_index.md` +- one initial version tree, for example `content/docs/1.0/...` +- `data/docs-versions.toml` -1. The same conceptual document must keep the same `doc_id` across versions. -2. Relative slug structure should stay aligned across versions whenever possible. -3. Canonical docs page URLs should be versioned, for example `/docs/1.1/end-user/...`. -4. Unversioned audience landing pages such as `/docs/end-user/` should point to the configured current version landing page, not duplicate content. -5. Individual docs pages should not exist at both unversioned and versioned URLs unless an intentional alias is needed. -6. Version folder names and URL slugs should use plain release identifiers such as `1.0` and `1.1`, not `v1.0` and `v1.1`. +Exit condition: -Version switcher behavior: +- Hugo builds a placeholder Sambee site without using `website-temp` content. -1. On a versioned docs page, show a version switcher containing the current version and older supported versions. -2. When a visitor selects another version, resolve the target page by matching: - - same `audience` - - same `doc_id` - - selected `product_version` -3. If no equivalent page exists in the selected version, send the visitor to that version's audience landing page and show a short notice that the exact page is unavailable in that version. -4. The switcher should be absent or disabled outside the docs subsite. +### Phase 2: Extract And Clean The Theme -Navigation and breadcrumb requirements: +Create `themes/sambee/` and copy the donor theme source. -- docs navigation must indicate both audience and selected version -- breadcrumbs should include the version level where helpful -- section sidebars should be generated per version and audience, not globally across all versions +Keep: -Search requirements: +- base templates +- essentials partials +- icons +- image render hooks +- generic article and docs shell pieces -- Pagefind results must include enough metadata to identify version and audience -- docs search UI should default to the selected version when the visitor is inside versioned docs -- optionally allow widening the search scope to all versions if that proves useful later +Remove or replace immediately: -Implementation note: +- blog home template +- blog section templates +- taxonomy and term templates +- comments partials +- helgeklein branding -The simplest robust approach is to build the version switcher on top of stable page metadata rather than path guessing. In practice, that means using `doc_id` and `product_version` in front matter and resolving cross-version matches from Hugo page collections. +Exit condition: -Exit criteria: +- The theme renders a generic Sambee page shell with no blog dependency. -- docs content exists in separate versioned trees -- the current version is centrally configurable -- cross-version page switching works or falls back predictably -- search can distinguish versions -- canonical docs URLs are versioned +### Phase 3: Rebuild The Asset And Search Pipeline -### Phase 7: Implement Required Search And Exclude Unneeded Features +Set up only the dependencies still needed for Sambee: -Goal: retain the required search experience while explicitly removing features that are out of scope. +- Tailwind CSS +- theme token generation +- Pagefind +- optional formatter tooling if wanted for the `website/` subproject -Required feature work: +Required scripts: -1. Search - - Keep Pagefind and integrate it into the docs experience. - - Ensure search works across versioned docs. - - Decide whether the default scope should be current version only or current section plus current version. -2. Taxonomies - - Avoid categories and tags unless the site includes a real blog. -3. RSS - - Keep only if news or blog content exists. -4. Related content widgets - - Use only if there is enough long-form editorial content. +- `dev` +- `build` +- `preview` +- `build:search` -Explicit exclusions: +Build requirements: -- no Giscus -- no other page-level commenting system -- no PWA manifest or install flow -- no PWA Hugo module +- generate `generated-theme.css` +- build the Hugo site +- run Pagefind against `website/public` -Exit criteria: +Exit condition: -- Pagefind works in the built site. -- Comments and PWA functionality are absent from the active implementation. +- local dev and production builds both generate a working search index. -### Phase 8: Add Validation, CI, And Deployment +### Phase 4: Implement Versioned Docs Behavior -Goal: harden the new site after the architecture and theme are stable. +Implement docs-specific templates and data-driven behavior for: -Steps: +- version switcher +- docs side navigation +- breadcrumbs +- version status banners +- hard redirects for unversioned docs book entry points -1. Add HTML validation only after routes are settled. -2. Port `.htmltest.yml` only if the generated site structure is similar enough. -3. Either rewrite or replace `tests/test_menu_urls.py` for the new menu model. -4. Add build commands to repository tooling. -5. Add deployment automation for `sambee.net` once the final hosting target is chosen. -6. Add checks that specifically validate versioned docs links and version switcher targets. -7. Add checks that validate the unversioned audience entry points resolve to the configured current version. +Implementation rules: -Exit criteria: +- use `doc_id` for cross-version matching +- do not infer equivalent pages from path alone +- keep the same docs book split inside every version +- keep one shared docs template for all books initially +- render sidebar order from `data/docs-nav/.toml` -- Site builds reproducibly and validation checks match the new site, not the old one. -- Production deployment assumptions are aligned with `sambee.net`. +Exit condition: -## Concrete File Migration Map +- users can move between supported versions predictably and understand which version they are viewing. -### Files To Copy Early +### Phase 5: Migrate Content And Harden The Site -Copy these early into the new `website/` tree for extraction work: +Migrate homepage and documentation material from the planning docs and existing internal docs. -- `website-temp/themes/helgeklein/assets/**` -> `website/themes/sambee/assets/**` -- `website-temp/themes/helgeklein/i18n/**` -> `website/themes/sambee/i18n/**` -- `website-temp/themes/helgeklein/layouts/**` -> `website/themes/sambee/layouts/**` -- `website-temp/data/theme.json` -> `website/data/theme.json` -- `website-temp/scripts/themeGenerator.js` -> `website/scripts/themeGenerator.js` -- Pagefind-related assets and templates that are actually used by the migrated theme +Then add: -### Files To Recreate Manually +- validation checks +- link checking +- version integrity checks +- deployment automation for `sambee.net` -- `website/hugo.toml` -- `website/config/_default/params.toml` -- `website/config/_default/menus.en.toml` -- `website/config/_default/module.toml` -- `website/data/docs-versions.toml` -- `website/content/**` +Required automated checks: -### Files To Copy Only If Needed Later +- missing `doc_id` +- duplicate `doc_id` within the same book and version +- broken cross-version switcher targets +- broken unversioned docs book entry points +- broken internal links in built docs +- docs navigation files reference only existing `doc_id` values +- each navigable page appears exactly once in the corresponding version navigation file -- `website-temp/package.json` -- `website-temp/package-lock.json` -- `website-temp/go.mod` -- `website-temp/go.sum` -- `website-temp/.htmltest.yml` -- `website-temp/tests/test_menu_urls.py` +Exit condition: -### Files To Ignore Completely +- the site is content-complete enough to replace the donor implementation path. -- `website-temp/content/**` -- `website-temp/public/**` -- `website-temp/resources/**` -- `website-temp/node_modules/**` -- `website-temp/migration/**` -- `website-temp/tmp/**` -- `website-temp/.git/**` -- `website-temp/static/manifest.webmanifest` +## Main Risks -## Risks And How To Avoid Them +### Hidden Blog Coupling In Donor Templates -### Risk: Hidden Blog Assumptions In Shared Partials +Risk: -Some partials look generic but still depend on blog-style fields, featured images, or post collections. +Generic-looking partials may still assume blog fields, featured images, post lists, or taxonomy terms. Mitigation: -- smoke-test every copied template against a minimal Sambee page before trusting it -- remove homepage and taxonomy templates first -- keep site root overrides available to replace theme templates quickly +- remove blog templates first +- smoke-test copied partials against minimal docs pages +- prefer explicit Sambee overrides over preserving donor behavior by default -### Risk: Versioned Docs Become Inconsistent Across Releases +### Version Drift Across Releases -Without a strong convention, versioned docs can drift in filenames, slugs, or page identity, making the version switcher unreliable. +Risk: + +Equivalent docs pages may stop matching across releases, breaking the switcher. Mitigation: -- require a stable `doc_id` on versioned docs pages -- keep per-version folder structures aligned wherever possible -- add automated checks for cross-version page resolution +- require `doc_id` +- keep folder structures aligned where possible +- validate cross-version mapping in CI + +### Ordering Drift Between Content And Reader Navigation -### Risk: Search Quality Degrades When Multiple Versions Are Indexed +Risk: -If all versions are indexed without metadata and scoping, visitors may get noisy or outdated results. +Folder order, titles, and other inferred ordering signals can drift away from the intended reading order, making navigation inconsistent. Mitigation: -- index version and audience metadata with each docs page -- default docs search to the selected version context -- clearly label search results with version badges +- treat `data/docs-nav/.toml` as the ordering source of truth +- validate that navigable pages are listed exactly once +- do not use a second ordering mechanism for docs pages -### Risk: Carrying Over Too Many Optional Dependencies +### Search Noise Across Multiple Versions -The copied site has Hugo modules, Tailwind, search tooling, and validation extras. +Risk: + +Users may get results from the wrong version. Mitigation: -- start with the smallest working set -- add modules and tooling back one feature at a time +- attach version and book metadata to indexed pages +- default search scope to the current version context +- hide or de-prioritize unsupported versions + +### Theme And Site Responsibilities Stay Mixed -### Risk: Theme And Site Responsibilities Remain Blurred +Risk: -If too much logic stays at the site root, future reuse and maintenance become harder. +The new site becomes hard to maintain if generic rendering logic stays in the site root. Mitigation: -- move generic presentation into `themes/sambee` -- keep Sambee content and configuration at the site root +- keep reusable presentation in `themes/sambee` +- keep content, configuration, and Sambee-specific overrides in the site root -### Risk: Generated Files Accidentally Enter Source Control +## Definition Of Done -The copied site contains several generated outputs. +The migration is structurally complete when all of the following are true: -Mitigation: +1. `website-temp` is no longer part of the active implementation path. +2. `website/` builds without donor content. +3. The active theme is `sambee`. +4. No Helge Klein branding, IDs, or donor-specific configuration remain in active site files. +5. Canonical docs URLs are versioned and use plain version slugs such as `1.0` and `1.1`. +6. Docs content lives under `docs//end-user`, `docs//admin`, and `docs//developer`. +7. Pagefind works and search results are version-aware. +8. Comments and PWA functionality are absent. +9. Version switching, docs book entry points, and docs navigation all resolve predictably. +10. Docs sidebar order is fully determined by explicit navigation data, not inferred ordering. + +## Immediate Next Step + +Scaffold `website/` with: + +- the Hugo root files +- `themes/sambee/` +- one initial versioned docs tree +- `data/docs-versions.toml` +- `data/docs-nav/1.0.toml` +- placeholder docs templates for navigation, version switching, and Pagefind-aware search -- define ignores for `website/public/`, `website/resources/`, and generated CSS artifacts where appropriate -- generate fresh outputs in the new site instead of copying them - -## Suggested Order Of Implementation Work - -1. Create `website/` skeleton -2. Create `themes/sambee/` -3. Copy theme assets, layouts, i18n, `theme.json`, and theme generator script -4. Strip blog-specific templates from the copied theme -5. Recreate minimal Hugo config for Sambee -6. Replace logos, colors, and typography tokens -7. Build a minimal homepage and one docs page -8. Implement versioned docs structure and version switcher support -9. Wire Pagefind into version-aware docs search -10. Add validation and deployment -11. Migrate actual documentation content - -## Definition Of Done For The Migration - -The migration should be considered structurally complete when all of the following are true: - -1. `website-temp` is no longer part of the active implementation path -2. `website/` builds without depending on blog content -3. no Helge Klein branding or IDs remain in active Sambee site files -4. the active theme is `sambee` -5. homepage and docs navigation match the Sambee planning docs -6. docs content is organized under `docs//end-user`, `docs//admin`, and `docs//developer` -7. versioned docs are stored separately per product version and can be switched predictably -8. Pagefind works for the active site -9. Giscus and PWA functionality are absent from the active site - -## Recommended Next Step - -The next implementation step should be to scaffold `website/` and extract only the minimum viable theme layer: - -- theme shell -- asset pipeline -- Sambee config -- placeholder homepage -- placeholder docs landing page -- versioned docs scaffolding for one initial product version -- Pagefind-enabled docs search shell - -After that, the remaining migration decisions become concrete and testable rather than theoretical. +That creates the minimum viable implementation foundation without dragging donor complexity into the new site. From 15887a1befe4683fc13a3409fe55e614dd002b0e Mon Sep 17 00:00:00 2001 From: Helge Klein Date: Mon, 6 Apr 2026 19:09:09 +0200 Subject: [PATCH 3/9] Hugo install in devcontainer --- .devcontainer/Dockerfile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index fdf1c7a..cf5c9a8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,7 @@ FROM mcr.microsoft.com/devcontainers/python:3.13 +ARG HUGO_VERSION=0.160.0 + # Install Sambee system dependencies from centralized script COPY scripts/install-system-deps /tmp/ RUN bash /tmp/install-system-deps && rm /tmp/install-system-deps @@ -11,6 +13,7 @@ COPY imagemagick-policy.xml /etc/ImageMagick-7/policy.xml RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends \ build-essential \ + curl \ libkrb5-dev \ smbclient \ cifs-utils \ @@ -25,6 +28,20 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ file \ && apt-get clean -y && rm -rf /var/lib/apt/lists/* +# Install Hugo Extended as a pinned devcontainer dependency. +RUN arch="$(dpkg --print-architecture)" \ + && case "$arch" in \ + amd64) hugo_arch="Linux-64bit" ;; \ + arm64) hugo_arch="Linux-ARM64" ;; \ + *) echo "Unsupported architecture for Hugo: $arch" >&2; exit 1 ;; \ + esac \ + && curl -fsSL \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_${hugo_arch}.tar.gz" \ + -o /tmp/hugo.tar.gz \ + && tar -xzf /tmp/hugo.tar.gz -C /tmp hugo \ + && install -m 0755 /tmp/hugo /usr/local/bin/hugo \ + && rm -f /tmp/hugo.tar.gz /tmp/hugo + # Use a minimal bash configuration USER vscode RUN printf '%s\n' 'export PATH="$HOME/.local/bin:$PATH"' > ~/.bashrc From 233bddca565c8c0e431fa7bd669f237c85f77c0b Mon Sep 17 00:00:00 2001 From: Helge Klein Date: Mon, 6 Apr 2026 20:21:53 +0200 Subject: [PATCH 4/9] Website scaffold --- website/.gitignore | 4 + website/config/_default/menus.en.toml | 9 ++ website/config/_default/module.toml | 3 + website/config/_default/params.toml | 1 + website/content/_index.md | 7 + website/content/docs/1.0/_index.md | 6 + website/content/docs/1.0/admin/_index.md | 8 ++ .../content/docs/1.0/admin/configuration.md | 8 ++ website/content/docs/1.0/developer/_index.md | 8 ++ .../docs/1.0/developer/architecture.md | 8 ++ website/content/docs/1.0/end-user/_index.md | 8 ++ website/content/docs/1.0/end-user/install.md | 8 ++ website/content/docs/_index.md | 5 + website/go.mod | 3 + website/hugo.toml | 18 +++ website/package-lock.json | 132 ++++++++++++++++++ website/package.json | 14 ++ website/static/_redirects | 6 + .../sambee/layouts/_default/baseof.html | 101 ++++++++++++++ .../themes/sambee/layouts/_default/list.html | 8 ++ .../sambee/layouts/_default/single.html | 8 ++ website/themes/sambee/layouts/docs/list.html | 53 +++++++ .../themes/sambee/layouts/docs/single.html | 12 ++ website/themes/sambee/layouts/home.html | 8 ++ .../sambee/layouts/partials/docs/sidebar.html | 42 ++++++ .../partials/docs/version-switcher.html | 22 +++ 26 files changed, 510 insertions(+) create mode 100644 website/.gitignore create mode 100644 website/config/_default/menus.en.toml create mode 100644 website/config/_default/module.toml create mode 100644 website/config/_default/params.toml create mode 100644 website/content/_index.md create mode 100644 website/content/docs/1.0/_index.md create mode 100644 website/content/docs/1.0/admin/_index.md create mode 100644 website/content/docs/1.0/admin/configuration.md create mode 100644 website/content/docs/1.0/developer/_index.md create mode 100644 website/content/docs/1.0/developer/architecture.md create mode 100644 website/content/docs/1.0/end-user/_index.md create mode 100644 website/content/docs/1.0/end-user/install.md create mode 100644 website/content/docs/_index.md create mode 100644 website/go.mod create mode 100644 website/hugo.toml create mode 100644 website/package-lock.json create mode 100644 website/package.json create mode 100644 website/static/_redirects create mode 100644 website/themes/sambee/layouts/_default/baseof.html create mode 100644 website/themes/sambee/layouts/_default/list.html create mode 100644 website/themes/sambee/layouts/_default/single.html create mode 100644 website/themes/sambee/layouts/docs/list.html create mode 100644 website/themes/sambee/layouts/docs/single.html create mode 100644 website/themes/sambee/layouts/home.html create mode 100644 website/themes/sambee/layouts/partials/docs/sidebar.html create mode 100644 website/themes/sambee/layouts/partials/docs/version-switcher.html diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..cd3e46e --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +public/ +resources/ +.hugo_build.lock diff --git a/website/config/_default/menus.en.toml b/website/config/_default/menus.en.toml new file mode 100644 index 0000000..ac47044 --- /dev/null +++ b/website/config/_default/menus.en.toml @@ -0,0 +1,9 @@ +[[main]] +name = "Home" +url = "/" +weight = 1 + +[[main]] +name = "Docs" +url = "/docs/" +weight = 2 diff --git a/website/config/_default/module.toml b/website/config/_default/module.toml new file mode 100644 index 0000000..c192622 --- /dev/null +++ b/website/config/_default/module.toml @@ -0,0 +1,3 @@ +[hugoVersion] +extended = true +min = "0.160.0" diff --git a/website/config/_default/params.toml b/website/config/_default/params.toml new file mode 100644 index 0000000..2bfc4ec --- /dev/null +++ b/website/config/_default/params.toml @@ -0,0 +1 @@ +description = "Browser-based SMB and local-drive access with versioned product documentation." diff --git a/website/content/_index.md b/website/content/_index.md new file mode 100644 index 0000000..1ae5fd0 --- /dev/null +++ b/website/content/_index.md @@ -0,0 +1,7 @@ ++++ +title = "Sambee" ++++ + +Sambee provides browser-based access to SMB shares and local drives. + +Start with the [documentation](/docs/) or jump directly to the current end-user guide at [End-User Docs](/docs/1.0/end-user/). diff --git a/website/content/docs/1.0/_index.md b/website/content/docs/1.0/_index.md new file mode 100644 index 0000000..b1053b8 --- /dev/null +++ b/website/content/docs/1.0/_index.md @@ -0,0 +1,6 @@ ++++ +title = "Documentation 1.0" +product_version = "1.0" ++++ + +Version 1.0 is the current published documentation set. diff --git a/website/content/docs/1.0/admin/_index.md b/website/content/docs/1.0/admin/_index.md new file mode 100644 index 0000000..d27385d --- /dev/null +++ b/website/content/docs/1.0/admin/_index.md @@ -0,0 +1,8 @@ ++++ +title = "Admin" +doc_id = "admin-index" +product_version = "1.0" +book = "admin" ++++ + +Documentation for administrators deploying and configuring Sambee. diff --git a/website/content/docs/1.0/admin/configuration.md b/website/content/docs/1.0/admin/configuration.md new file mode 100644 index 0000000..fa99d0a --- /dev/null +++ b/website/content/docs/1.0/admin/configuration.md @@ -0,0 +1,8 @@ ++++ +title = "Configuration" +doc_id = "configuration" +product_version = "1.0" +book = "admin" ++++ + +This is a placeholder admin page for the initial website scaffold. diff --git a/website/content/docs/1.0/developer/_index.md b/website/content/docs/1.0/developer/_index.md new file mode 100644 index 0000000..51f3462 --- /dev/null +++ b/website/content/docs/1.0/developer/_index.md @@ -0,0 +1,8 @@ ++++ +title = "Developer" +doc_id = "developer-index" +product_version = "1.0" +book = "developer" ++++ + +Documentation for contributors and developers working on Sambee. diff --git a/website/content/docs/1.0/developer/architecture.md b/website/content/docs/1.0/developer/architecture.md new file mode 100644 index 0000000..a8149ff --- /dev/null +++ b/website/content/docs/1.0/developer/architecture.md @@ -0,0 +1,8 @@ ++++ +title = "Architecture" +doc_id = "architecture" +product_version = "1.0" +book = "developer" ++++ + +This is a placeholder developer page for the initial website scaffold. diff --git a/website/content/docs/1.0/end-user/_index.md b/website/content/docs/1.0/end-user/_index.md new file mode 100644 index 0000000..c48a8b2 --- /dev/null +++ b/website/content/docs/1.0/end-user/_index.md @@ -0,0 +1,8 @@ ++++ +title = "End-User" +doc_id = "end-user-index" +product_version = "1.0" +book = "end-user" ++++ + +Documentation for people using Sambee day to day. diff --git a/website/content/docs/1.0/end-user/install.md b/website/content/docs/1.0/end-user/install.md new file mode 100644 index 0000000..1b16c75 --- /dev/null +++ b/website/content/docs/1.0/end-user/install.md @@ -0,0 +1,8 @@ ++++ +title = "Install Sambee" +doc_id = "install" +product_version = "1.0" +book = "end-user" ++++ + +This is a placeholder end-user page for the initial website scaffold. diff --git a/website/content/docs/_index.md b/website/content/docs/_index.md new file mode 100644 index 0000000..54a41a8 --- /dev/null +++ b/website/content/docs/_index.md @@ -0,0 +1,5 @@ ++++ +title = "Documentation" ++++ + +Sambee documentation is versioned. Use the current documentation set unless you intentionally need a different product version. diff --git a/website/go.mod b/website/go.mod new file mode 100644 index 0000000..be57dc8 --- /dev/null +++ b/website/go.mod @@ -0,0 +1,3 @@ +module sambee.net/website + +go 1.24.0 diff --git a/website/hugo.toml b/website/hugo.toml new file mode 100644 index 0000000..b6be94b --- /dev/null +++ b/website/hugo.toml @@ -0,0 +1,18 @@ +baseURL = "https://sambee.net/" +title = "Sambee" +theme = "sambee" +defaultContentLanguage = "en" +enableRobotsTXT = true + +[pagination] +pagerSize = 10 + +[outputs] +home = ["HTML"] +section = ["HTML"] +page = ["HTML"] + +[markup] + [markup.goldmark] + [markup.goldmark.renderer] + unsafe = true diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..3048e8f --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,132 @@ +{ + "name": "sambee-website", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sambee-website", + "version": "0.1.0", + "devDependencies": { + "pagefind": "^1.4.0" + } + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.0.tgz", + "integrity": "sha512-OXQVlxALU9+Lz/LxkAa+RvaxY1cnRKUDCuwl9o8PY5Lg/znP573y4WIbVOOIz8Bv7uj7r00TGy7pD+NSLMJGBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.0.tgz", + "integrity": "sha512-+LK1Xq5n/B0hHc08DW61SnfIlfLKyXZ1oKcbfZ1MimE7Rn0Q6R0VI/TlC04f/JDPm+67zAOwPGizzvefOi5vqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.0.tgz", + "integrity": "sha512-kicDfUF9gn/z06NimTwNlZXF8z3pLsN3BIPPt6N8unuh0n55fr64tVs2p3a5RKYmQkJGjPfOE/C9GI5YTEpURg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.0.tgz", + "integrity": "sha512-e5rDB3wPm89bcSLiatKBDTrVTbsMQrrtkXRaAoUJYU0C1suXVvEzZfjmMvrUDvYhZBx/Ls8hGuGxlqSJBz3gDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.0.tgz", + "integrity": "sha512-vh52DcBiF/mRMmq+Rwt3M3RgEWgl00jFk/M5NWhLEHJFq4+papQXwbyKbi7cNlxaeYrKx6wOfW3fm9cftfc/Kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.0.tgz", + "integrity": "sha512-kg+szZwffZdyWn6SL6RHjAYjhSvJ2bT4qkv3KepGsbmD9fuSHUSC+2kydDneDVUA9qEDRf9uSFoEAsXsp1/JKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.0.tgz", + "integrity": "sha512-8eOCmB8lnpyvwz+HrcTXLuBxhj7UseAFh6KGEXRe8UCcAfVQih+qPy/4akJRezViI+ONijz9oi7HpMkw9rdtBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/pagefind": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.0.tgz", + "integrity": "sha512-7vQ2xh0ZmjPjsuWONR68nqzb+QNfpPh7pdT6n6YDAthWAQiUkSACVegSswY5zPNONGYFWebFVgdnS5/m/Qqn+w==", + "dev": true, + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.0", + "@pagefind/darwin-x64": "1.5.0", + "@pagefind/freebsd-x64": "1.5.0", + "@pagefind/linux-arm64": "1.5.0", + "@pagefind/linux-x64": "1.5.0", + "@pagefind/windows-arm64": "1.5.0", + "@pagefind/windows-x64": "1.5.0" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..6ca1556 --- /dev/null +++ b/website/package.json @@ -0,0 +1,14 @@ +{ + "name": "sambee-website", + "private": true, + "version": "0.1.0", + "scripts": { + "dev": "hugo server --source . --bind 0.0.0.0 --port 1313 --disableFastRender", + "build": "hugo --source . --cleanDestinationDir", + "preview": "hugo server --source . --environment production --disableFastRender", + "build:search": "npx pagefind --site public" + }, + "devDependencies": { + "pagefind": "^1.4.0" + } +} diff --git a/website/static/_redirects b/website/static/_redirects new file mode 100644 index 0000000..4a83014 --- /dev/null +++ b/website/static/_redirects @@ -0,0 +1,6 @@ +/docs/end-user /docs/1.0/end-user/ 301 +/docs/end-user/ /docs/1.0/end-user/ 301 +/docs/admin /docs/1.0/admin/ 301 +/docs/admin/ /docs/1.0/admin/ 301 +/docs/developer /docs/1.0/developer/ 301 +/docs/developer/ /docs/1.0/developer/ 301 diff --git a/website/themes/sambee/layouts/_default/baseof.html b/website/themes/sambee/layouts/_default/baseof.html new file mode 100644 index 0000000..2d0d268 --- /dev/null +++ b/website/themes/sambee/layouts/_default/baseof.html @@ -0,0 +1,101 @@ + + + + + + {{ if .Title }}{{ .Title }} | {{ site.Title }}{{ else }}{{ site.Title }}{{ end }} + + + + +
+ + +
+
+ {{ block "main" . }}{{ end }} +
+
+ Sambee website scaffold for Hugo migration validation. +
+ + diff --git a/website/themes/sambee/layouts/_default/list.html b/website/themes/sambee/layouts/_default/list.html new file mode 100644 index 0000000..0e9fa66 --- /dev/null +++ b/website/themes/sambee/layouts/_default/list.html @@ -0,0 +1,8 @@ +{{ define "main" }} +
+

{{ .Title }}

+
+ {{ .Content }} +
+
+{{ end }} diff --git a/website/themes/sambee/layouts/_default/single.html b/website/themes/sambee/layouts/_default/single.html new file mode 100644 index 0000000..0e9fa66 --- /dev/null +++ b/website/themes/sambee/layouts/_default/single.html @@ -0,0 +1,8 @@ +{{ define "main" }} +
+

{{ .Title }}

+
+ {{ .Content }} +
+
+{{ end }} diff --git a/website/themes/sambee/layouts/docs/list.html b/website/themes/sambee/layouts/docs/list.html new file mode 100644 index 0000000..1906bfd --- /dev/null +++ b/website/themes/sambee/layouts/docs/list.html @@ -0,0 +1,53 @@ +{{ define "main" }} +{{ $currentVersion := hugo.Data.docs_versions.current }} +{{ $navData := index hugo.Data.docs_nav $currentVersion }} + +{{ if and (not .Params.product_version) (not .Params.book) }} +
+

{{ .Title }}

+
{{ .Content }}
+ {{ with $navData }} +
+ {{ range .books }} +
+

{{ .title }}

+

Current version: {{ $currentVersion }}

+
+ {{ end }} +
+ {{ end }} +
+{{ else if and .Params.product_version (not .Params.book) }} +
+

{{ .Title }}

+
{{ .Content }}
+ {{ $versionNav := index hugo.Data.docs_nav .Params.product_version }} + {{ with $versionNav }} +
+ {{ range .books }} +
+

{{ .title }}

+

Book for Sambee {{ $.Params.product_version }}

+
+ {{ end }} +
+ {{ end }} +
+{{ else if and .Params.product_version .Params.book }} +
+ +
+ {{ partial "docs/version-switcher.html" . }} +

{{ .Title }}

+
{{ .Content }}
+
+
+{{ else }} +
+

{{ .Title }}

+
{{ .Content }}
+
+{{ end }} +{{ end }} diff --git a/website/themes/sambee/layouts/docs/single.html b/website/themes/sambee/layouts/docs/single.html new file mode 100644 index 0000000..d874a08 --- /dev/null +++ b/website/themes/sambee/layouts/docs/single.html @@ -0,0 +1,12 @@ +{{ define "main" }} +
+ +
+ {{ partial "docs/version-switcher.html" . }} +

{{ .Title }}

+
{{ .Content }}
+
+
+{{ end }} diff --git a/website/themes/sambee/layouts/home.html b/website/themes/sambee/layouts/home.html new file mode 100644 index 0000000..0e9fa66 --- /dev/null +++ b/website/themes/sambee/layouts/home.html @@ -0,0 +1,8 @@ +{{ define "main" }} +
+

{{ .Title }}

+
+ {{ .Content }} +
+
+{{ end }} diff --git a/website/themes/sambee/layouts/partials/docs/sidebar.html b/website/themes/sambee/layouts/partials/docs/sidebar.html new file mode 100644 index 0000000..6951ddb --- /dev/null +++ b/website/themes/sambee/layouts/partials/docs/sidebar.html @@ -0,0 +1,42 @@ +{{ $version := .Params.product_version }} +{{ $book := .Params.book }} +{{ $navData := index hugo.Data.docs_nav $version }} +{{ $bookPage := site.GetPage (printf "/docs/%s/%s" $version $book) }} +{{ $bookPages := where (where site.RegularPages "Params.product_version" $version) "Params.book" $book }} + +{{ if and $navData $book }} + {{ range $navData.books }} + {{ if eq .slug $book }} + + {{ end }} + {{ end }} +{{ end }} diff --git a/website/themes/sambee/layouts/partials/docs/version-switcher.html b/website/themes/sambee/layouts/partials/docs/version-switcher.html new file mode 100644 index 0000000..332c731 --- /dev/null +++ b/website/themes/sambee/layouts/partials/docs/version-switcher.html @@ -0,0 +1,22 @@ +{{ $currentVersion := .Params.product_version }} +{{ $book := .Params.book }} +{{ $docId := .Params.doc_id }} +
+ Version: + {{ range $version := hugo.Data.docs_versions.versions }} + {{ if eq $version.slug $currentVersion }} + {{ $version.label }} + {{ else }} + {{ $targetPages := where (where site.RegularPages "Params.product_version" $version.slug) "Params.book" $book }} + {{ $targetMatches := where $targetPages "Params.doc_id" $docId }} + {{ if gt (len $targetMatches) 0 }} + {{ $target := index $targetMatches 0 }} + {{ $version.label }} + {{ else }} + {{ with site.GetPage (printf "/docs/%s/%s" $version.slug $book) }} + {{ $version.label }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} +
From 9d68c52862d4fbb9a603e619d03061c90ad2e0fc Mon Sep 17 00:00:00 2001 From: Helge Klein Date: Mon, 6 Apr 2026 20:34:01 +0200 Subject: [PATCH 5/9] Theme migration --- website/assets/css/generated-theme.css | 56 + website/config/_default/menus.en.toml | 10 + website/config/_default/params.toml | 6 + website/hugo.toml | 4 + website/hugo_stats.json | 100 ++ website/i18n/en.toml | 53 + website/package-lock.json | 1069 ++++++++++++++++- website/package.json | 14 +- website/scripts/themeGenerator.js | 141 +++ website/themes/sambee/assets/css/article.css | 26 + website/themes/sambee/assets/css/base.css | 217 ++++ website/themes/sambee/assets/css/buttons.css | 34 + website/themes/sambee/assets/css/code.css | 102 ++ website/themes/sambee/assets/css/content.css | 215 ++++ website/themes/sambee/assets/css/custom.css | 198 +++ website/themes/sambee/assets/css/footer.css | 63 + website/themes/sambee/assets/css/main.css | 54 + website/themes/sambee/assets/css/modal.css | 1 + .../sambee/assets/css/module-overrides.css | 4 + .../themes/sambee/assets/css/navigation.css | 654 ++++++++++ .../themes/sambee/assets/css/posts-grid.css | 110 ++ website/themes/sambee/assets/css/search.css | 357 ++++++ website/themes/sambee/assets/css/syntax.css | 1 + website/themes/sambee/assets/js/main.js | 5 + website/themes/sambee/assets/js/navigation.js | 156 +++ .../sambee/layouts/_default/baseof.html | 102 +- .../themes/sambee/layouts/_default/list.html | 14 +- .../sambee/layouts/_default/single.html | 14 +- website/themes/sambee/layouts/docs/list.html | 58 +- .../themes/sambee/layouts/docs/single.html | 12 +- website/themes/sambee/layouts/home.html | 56 +- .../sambee/layouts/partials/docs/sidebar.html | 14 +- .../partials/docs/version-switcher.html | 8 +- .../layouts/partials/essentials/footer.html | 14 + .../layouts/partials/essentials/head.html | 17 + .../layouts/partials/essentials/header.html | 53 + .../layouts/partials/essentials/script.html | 8 + .../layouts/partials/essentials/style.html | 16 + .../layouts/partials/icons/chevron-right.html | 12 + .../sambee/layouts/partials/icons/clock.html | 2 + .../sambee/layouts/partials/icons/folder.html | 2 + .../sambee/layouts/partials/icons/github.html | 2 + .../layouts/partials/icons/linkedin.html | 2 + .../sambee/layouts/partials/icons/rss.html | 2 + .../sambee/layouts/partials/icons/search.html | 2 + .../sambee/layouts/partials/icons/tag.html | 2 + .../sambee/layouts/partials/icons/user.html | 2 + .../themes/sambee/layouts/partials/logo.html | 23 + .../sambee/layouts/partials/search-modal.html | 348 ++++++ 49 files changed, 4282 insertions(+), 153 deletions(-) create mode 100644 website/assets/css/generated-theme.css create mode 100644 website/hugo_stats.json create mode 100644 website/i18n/en.toml create mode 100644 website/scripts/themeGenerator.js create mode 100644 website/themes/sambee/assets/css/article.css create mode 100755 website/themes/sambee/assets/css/base.css create mode 100755 website/themes/sambee/assets/css/buttons.css create mode 100644 website/themes/sambee/assets/css/code.css create mode 100644 website/themes/sambee/assets/css/content.css create mode 100644 website/themes/sambee/assets/css/custom.css create mode 100644 website/themes/sambee/assets/css/footer.css create mode 100755 website/themes/sambee/assets/css/main.css create mode 100644 website/themes/sambee/assets/css/modal.css create mode 100644 website/themes/sambee/assets/css/module-overrides.css create mode 100755 website/themes/sambee/assets/css/navigation.css create mode 100644 website/themes/sambee/assets/css/posts-grid.css create mode 100644 website/themes/sambee/assets/css/search.css create mode 100644 website/themes/sambee/assets/css/syntax.css create mode 100755 website/themes/sambee/assets/js/main.js create mode 100644 website/themes/sambee/assets/js/navigation.js create mode 100644 website/themes/sambee/layouts/partials/essentials/footer.html create mode 100644 website/themes/sambee/layouts/partials/essentials/head.html create mode 100644 website/themes/sambee/layouts/partials/essentials/header.html create mode 100644 website/themes/sambee/layouts/partials/essentials/script.html create mode 100644 website/themes/sambee/layouts/partials/essentials/style.html create mode 100644 website/themes/sambee/layouts/partials/icons/chevron-right.html create mode 100644 website/themes/sambee/layouts/partials/icons/clock.html create mode 100644 website/themes/sambee/layouts/partials/icons/folder.html create mode 100644 website/themes/sambee/layouts/partials/icons/github.html create mode 100644 website/themes/sambee/layouts/partials/icons/linkedin.html create mode 100644 website/themes/sambee/layouts/partials/icons/rss.html create mode 100644 website/themes/sambee/layouts/partials/icons/search.html create mode 100644 website/themes/sambee/layouts/partials/icons/tag.html create mode 100644 website/themes/sambee/layouts/partials/icons/user.html create mode 100644 website/themes/sambee/layouts/partials/logo.html create mode 100644 website/themes/sambee/layouts/partials/search-modal.html diff --git a/website/assets/css/generated-theme.css b/website/assets/css/generated-theme.css new file mode 100644 index 0000000..3810a3a --- /dev/null +++ b/website/assets/css/generated-theme.css @@ -0,0 +1,56 @@ +/** + * Auto-generated from "data/theme.json" + * DO NOT EDIT THIS FILE MANUALLY + * Run: node scripts/themeGenerator.js + */ + +@theme { + /* === Colors === */ + --color-clr-primary: #00CC9E; + --color-clr-secondary: #D1004D; + --color-clr-body: #FFFFFF; + --color-clr-border: #A2A2A2; + --color-clr-light: #F4F4F4; + --color-clr-dark: #303030; + --color-clr-code-block-background: #F4F4F4; + --color-clr-code-inline-background: #F0F0F0; + --color-clr-text: #303030; + --color-clr-text-dark: #343434; + --color-clr-text-light: #A2A2A2; + + /* === Darkmode Colors === */ + --color-clr-darkmode-primary: #21BA98; + --color-clr-darkmode-secondary: #D85E8C; + --color-clr-darkmode-body: #1C1C1C; + --color-clr-darkmode-border: #3E3E3E; + --color-clr-darkmode-light: #222222; + --color-clr-darkmode-dark: #FFFFFF; + --color-clr-darkmode-code-inline-background: #343434; + --color-clr-darkmode-code-block-background: #2F2F2F; + --color-clr-darkmode-text: #B4AFB6; + --color-clr-darkmode-text-dark: #CCCCCC; + --color-clr-darkmode-text-light: #909090; + + /* === Font Families === */ + --font-primary: Inter, sans-serif; + --font-secondary: Playfair Display, sans-serif; + --font-tertiary: Barlow Condensed, sans-serif; + + /* === Font Sizes === */ + --text-size-base: 16px; + --text-size-base-sm: 14.4px; + + /* === Heading Size Variables === */ + --text-size-h1: 2.25rem; + --text-size-h1-sm: 2.0250rem; + --text-size-h2: 2.25rem; + --text-size-h2-sm: 2.0250rem; + --text-size-h3: 1.75rem; + --text-size-h3-sm: 1.5750rem; + --text-size-h4: 1.25rem; + --text-size-h4-sm: 1.1250rem; + --text-size-h5: 1rem; + --text-size-h5-sm: 0.9000rem; + --text-size-h6: 1rem; + --text-size-h6-sm: 0.9000rem; +} diff --git a/website/config/_default/menus.en.toml b/website/config/_default/menus.en.toml index ac47044..c7203db 100644 --- a/website/config/_default/menus.en.toml +++ b/website/config/_default/menus.en.toml @@ -7,3 +7,13 @@ weight = 1 name = "Docs" url = "/docs/" weight = 2 + +[[footer]] +name = "Home" +url = "/" +weight = 1 + +[[footer]] +name = "Docs" +url = "/docs/" +weight = 2 diff --git a/website/config/_default/params.toml b/website/config/_default/params.toml index 2bfc4ec..e75b991 100644 --- a/website/config/_default/params.toml +++ b/website/config/_default/params.toml @@ -1 +1,7 @@ description = "Browser-based SMB and local-drive access with versioned product documentation." +theme_default = "light" +theme_switcher = false + +[search] +enable = true +show_image = false diff --git a/website/hugo.toml b/website/hugo.toml index b6be94b..f89f09b 100644 --- a/website/hugo.toml +++ b/website/hugo.toml @@ -7,6 +7,10 @@ enableRobotsTXT = true [pagination] pagerSize = 10 +[build] + [build.buildStats] + enable = true + [outputs] home = ["HTML"] section = ["HTML"] diff --git a/website/hugo_stats.json b/website/hugo_stats.json new file mode 100644 index 0000000..0d9679e --- /dev/null +++ b/website/hugo_stats.json @@ -0,0 +1,100 @@ +{ + "htmlElements": { + "tags": [ + "a", + "article", + "aside", + "body", + "button", + "div", + "footer", + "h1", + "h2", + "head", + "header", + "html", + "input", + "kbd", + "label", + "li", + "link", + "main", + "meta", + "nav", + "p", + "path", + "script", + "section", + "span", + "strong", + "svg", + "title", + "ul" + ], + "classes": [ + "block", + "btn", + "btn-bg-secondary", + "btn-outline-secondary", + "button-row", + "container", + "content", + "docs-content", + "docs-header", + "docs-meta", + "docs-meta-chip", + "docs-overview", + "docs-shell", + "docs-sidebar", + "eyebrow", + "footer", + "footer-bar", + "footer-copyright", + "footer-link", + "footer-links", + "header", + "header-shell", + "hero", + "hero-card", + "hero-copy", + "hero-stats", + "hidden", + "hr-border-bottom", + "lede", + "nav-actions", + "nav-bar", + "nav-item", + "nav-link", + "nav-logo", + "nav-logo-link", + "nav-menu", + "nav-search-button", + "nav-toggle", + "nav-toggle-bar", + "search-empty", + "search-icon", + "search-info", + "search-modal", + "search-modal-overlay", + "search-reset", + "search-results", + "search-wrapper", + "search-wrapper-body", + "search-wrapper-footer", + "search-wrapper-header", + "shell-section", + "site-main", + "size-[1.15rem]", + "spaced-list", + "sr-only", + "stat-card", + "version-switcher", + "version-switcher-label" + ], + "ids": [ + "nav-menu", + "search-input", + "search-modal" + ] + } +} diff --git a/website/i18n/en.toml b/website/i18n/en.toml new file mode 100644 index 0000000..aadc3f5 --- /dev/null +++ b/website/i18n/en.toml @@ -0,0 +1,53 @@ +# English translations + +[related_posts] +other = "Related Posts" + +[latest_posts] +other = "Latest Posts" + +[home] +other = "Home" + +[read_more] +other = "Read more" + +[categories] +other = "Categories" + +[category] +other = "Category" + +[tags] +other = "Tags" + +[tag] +other = "Tag" + +[posts_count] +one = "{{ .Count }} post" +other = "{{ .Count }} posts" + +[post] +other = "post" + +[posts] +other = "posts" + +[latest_blog_post] +other = "Latest blog post" + +[published] +other = "Published" + +[updated] +other = "Updated" + +[on_this_page] +other = "On this page" + +[search_placeholder] +other = "Type to search..." + +[search_initial] +other = "Start typing to search..." diff --git a/website/package-lock.json b/website/package-lock.json index 3048e8f..48fec0b 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -8,7 +8,60 @@ "name": "sambee-website", "version": "0.1.0", "devDependencies": { - "pagefind": "^1.4.0" + "@tailwindcss/cli": "^4.1.18", + "pagefind": "^1.4.0", + "tailwind-bootstrap-grid": "^6.0.0", + "tailwindcss": "^4.1.18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@pagefind/darwin-arm64": { @@ -109,6 +162,943 @@ "win32" ] }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/cli": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.2.2.tgz", + "integrity": "sha512-iJS+8kAFZ8HPqnh0O5DHCLjo4L6dD97DBQEkrhfSO4V96xeefUus2jqsBs1dUMt3OU9Ks4qIkiY0mpL5UW+4LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.5.1", + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "enhanced-resolve": "^5.19.0", + "mri": "^1.2.0", + "picocolors": "^1.1.1", + "tailwindcss": "4.2.2" + }, + "bin": { + "tailwindcss": "dist/index.mjs" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pagefind": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.0.tgz", @@ -127,6 +1117,83 @@ "@pagefind/windows-arm64": "1.5.0", "@pagefind/windows-x64": "1.5.0" } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwind-bootstrap-grid": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tailwind-bootstrap-grid/-/tailwind-bootstrap-grid-6.0.0.tgz", + "integrity": "sha512-BgdCV/6fAq7dmu1Rv9MwCj0ZCHXcbDRy5JpMyWUxuVIMUzzE5EobVkd/eU+ImD4Vi8aa9G5OhLOAKsHXO7Fgkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.24.4" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "tailwindcss": "^4" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/website/package.json b/website/package.json index 6ca1556..4ad2981 100644 --- a/website/package.json +++ b/website/package.json @@ -3,12 +3,16 @@ "private": true, "version": "0.1.0", "scripts": { - "dev": "hugo server --source . --bind 0.0.0.0 --port 1313 --disableFastRender", - "build": "hugo --source . --cleanDestinationDir", - "preview": "hugo server --source . --environment production --disableFastRender", - "build:search": "npx pagefind --site public" + "dev": "node scripts/themeGenerator.js && hugo server --source . --bind 0.0.0.0 --port 1313 --disableFastRender", + "dev:search": "node scripts/themeGenerator.js && hugo --source . --cleanDestinationDir && npx pagefind --site public", + "build": "node scripts/themeGenerator.js && hugo --source . --cleanDestinationDir", + "preview": "node scripts/themeGenerator.js && hugo server --source . --environment production --disableFastRender", + "build:search": "node scripts/themeGenerator.js && hugo --source . --cleanDestinationDir && npx pagefind --site public" }, "devDependencies": { - "pagefind": "^1.4.0" + "@tailwindcss/cli": "^4.1.18", + "pagefind": "^1.4.0", + "tailwind-bootstrap-grid": "^6.0.0", + "tailwindcss": "^4.1.18" } } diff --git a/website/scripts/themeGenerator.js b/website/scripts/themeGenerator.js new file mode 100644 index 0000000..8ade623 --- /dev/null +++ b/website/scripts/themeGenerator.js @@ -0,0 +1,141 @@ +const fs = require("fs"); +const path = require("path"); + +// Simple project paths - no theme/exampleSite detection needed +const themePath = path.join(__dirname, "../data/theme.json"); +const outputPath = path.join(__dirname, "../assets/css/generated-theme.css"); + +// Convert snake_case to kebab-case +const toKebab = (str) => str.replace(/_/g, "-"); + +// Extract font name (remove + and variants) +const findFont = (fontStr) => fontStr.replace(/\+/g, " ").replace(/:[^:]+/g, ""); + +// Add color CSS variables with "clr-" prefix for clear Tailwind utility names +// e.g., --color-clr-primary → text-clr-primary, bg-clr-primary +function addColorsToCss(cssLines, colors, prefix = "") { + Object.entries(colors).forEach(([key, value]) => { + const colorName = prefix + ? `--color-clr-${prefix}-${toKebab(key)}` + : `--color-clr-${toKebab(key)}`; + cssLines.push(` ${colorName}: ${value};`); + }); +} + +// Generate theme CSS from theme.json +function generateThemeCSS() { + if (!fs.existsSync(themePath)) { + throw new Error(`Theme file not found: ${themePath}`); + } + + const themeConfig = JSON.parse(fs.readFileSync(themePath, "utf8")); + + if (!themeConfig.colors || !themeConfig.fonts) { + throw new Error("Invalid theme.json: missing 'colors' or 'fonts'"); + } + + const cssLines = [ + "/**", + ' * Auto-generated from "data/theme.json"', + " * DO NOT EDIT THIS FILE MANUALLY", + " * Run: node scripts/themeGenerator.js", + " */", + "", + "@theme {", + " /* === Colors === */", + ]; + + // Default colors + if (themeConfig.colors.default?.theme_color) { + addColorsToCss(cssLines, themeConfig.colors.default.theme_color); + } + if (themeConfig.colors.default?.text_color) { + addColorsToCss(cssLines, themeConfig.colors.default.text_color); + } + + // Darkmode colors + if (themeConfig.colors.darkmode) { + cssLines.push("", " /* === Darkmode Colors === */"); + if (themeConfig.colors.darkmode.theme_color) { + addColorsToCss(cssLines, themeConfig.colors.darkmode.theme_color, "darkmode"); + } + if (themeConfig.colors.darkmode.text_color) { + addColorsToCss(cssLines, themeConfig.colors.darkmode.text_color, "darkmode"); + } + } + + // Font families + cssLines.push("", " /* === Font Families === */"); + const fontFamily = themeConfig.fonts.font_family || {}; + Object.entries(fontFamily) + .filter(([key]) => !key.includes("type")) + .forEach(([key, font]) => { + const fontFallback = fontFamily[`${key}_type`] || "sans-serif"; + const fontValue = `${findFont(font)}, ${fontFallback}`; + cssLines.push(` --font-${toKebab(key)}: ${fontValue};`); + }); + + // Font sizes + cssLines.push("", " /* === Font Sizes === */"); + const baseSize = Number(themeConfig.fonts.font_size?.base || 16); + const scale_sm = Number(themeConfig.fonts.font_size?.scale_sm || 0.9); + + cssLines.push(` --text-size-base: ${baseSize}px;`); + cssLines.push(` --text-size-base-sm: ${baseSize * scale_sm}px;`); + + // Heading size variables from theme.json + if (themeConfig.fonts.heading_sizes) { + cssLines.push("", " /* === Heading Size Variables === */"); + for (let i = 1; i <= 6; i++) { + const size = themeConfig.fonts.heading_sizes[`h${i}`]; + if (size) { + cssLines.push(` --text-size-h${i}: ${size};`); + // Parse rem value and create -sm variant + const remMatch = size.match(/([\d.]+)rem/); + if (remMatch) { + const remValue = parseFloat(remMatch[1]); + cssLines.push(` --text-size-h${i}-sm: ${(remValue * scale_sm).toFixed(4)}rem;`); + } + } + } + } + + cssLines.push("}"); + + // Write the file + const outputDir = path.dirname(outputPath); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + fs.writeFileSync(outputPath, cssLines.join("\n") + "\n"); + console.log("✅ Theme CSS generated:", outputPath); +} + +// Run on startup +try { + generateThemeCSS(); +} catch (error) { + console.error("❌ Error:", error.message); + process.exit(1); +} + +// Watch mode +if (process.argv.includes("--watch")) { + let debounceTimer; + + fs.watch(themePath, (eventType) => { + if (eventType === "change") { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + try { + generateThemeCSS(); + } catch (error) { + console.error("❌ Error:", error.message); + } + }, 300); + } + }); + + console.log("👁️ Watching:", themePath); +} diff --git a/website/themes/sambee/assets/css/article.css b/website/themes/sambee/assets/css/article.css new file mode 100644 index 0000000..265d9ac --- /dev/null +++ b/website/themes/sambee/assets/css/article.css @@ -0,0 +1,26 @@ +/* Article layout: content + TOC sidebar */ + +/* Flex container for article + TOC - centered and constrained */ +.article-toc-container { + display: flex; + gap: 2rem; + margin: 0 auto; + align-items: flex-start; + width: 100%; + justify-content: center; +} + +/* Article wrapper - flexible width, grows to fill available space */ +.article-wrapper { + /* Grow to fill available space */ + flex: 1 1 auto; + /* Allow shrinking below content size */ + min-width: 0; + /* Cap at article content width if there's room */ + max-width: var(--article-content-width); +} + +/* Article Content Width */ +.article-content { + max-width: var(--article-content-width); +} diff --git a/website/themes/sambee/assets/css/base.css b/website/themes/sambee/assets/css/base.css new file mode 100755 index 0000000..8f84c47 --- /dev/null +++ b/website/themes/sambee/assets/css/base.css @@ -0,0 +1,217 @@ +/* +============================== +Variables +============================== +*/ +:root { + /* Header height fallback - updated dynamically via JavaScript */ + --header-height: 87px; + /* Space between header and content; defined in baseof.html */ + --header-bottom-margin: 40px; + /* TOC position top (updates when JS sets the header height): header height + header bottom margin */ + --toc-position-top: calc(var(--header-height) + var(--header-bottom-margin)); + /* Container width: 815px content + 32px gap + 260px TOC + 24px container padding = 1131px */ + --article-container-width: 1131px; + --article-content-width: 815px; + --article-toc-width: 260px; + /* Code font size - shared between inline code and code blocks */ + --code-font-size: 0.875em; + /* Block element bottom margin - paragraphs, lists, blockquotes, tables, figures, pre */ + --content-block-margin: 1em; +} + +/* +============================== +HTML & Body +============================== +*/ +/* + Smooth scrolling when clicking TOC links. + Initially disabled (via html:not(.scroll-smooth)) to allow browser scroll restoration on refresh. + JavaScript adds .scroll-smooth class after page load to enable smooth scrolling. +*/ +html { + scroll-padding-top: var(--header-height); +} + +html.scroll-smooth { + scroll-behavior: smooth; +} + +/* Body */ +body { + /* Flex column layout: + - Makes main content expand to fill available space + - Pushes footer to bottom if content is short */ + @apply flex flex-col min-h-screen; + + /* Background color */ + @apply bg-clr-body dark:bg-clr-darkmode-body; + + /* Text color */ + @apply text-clr-text dark:text-clr-darkmode-text; + + /* Text size, line height & font family and font weight */ + @apply text-size-base font-primary font-normal leading-normal; +} + +/* +============================== +Headings +============================== +*/ +/* Headings: common style */ +h1, +h2, +h3, +h4, +h5, +h6 { + @apply font-bold leading-tight text-clr-text-dark dark:text-clr-darkmode-text-dark; +} + +/* Headings fonts: H1 */ +h1 { + @apply font-secondary; +} + +/* Headings fonts: H2-H6 */ +h2, +h3, +h4, +h5, +h6 { + @apply font-tertiary; +} + +/* Headings fonts: H5 */ +h5 { + @apply font-primary; +} + +/* Headings colors: H3 */ +h3, h5 { + @apply text-clr-primary; + + .dark & { + @apply text-clr-darkmode-primary; + } +} + +/* Heading font sizes and line heights */ +/* Note: + - The base size for rem calculations comes from the HTML element (browser default typically 16px) + - Format: text-size-[size]/[line-height] +*/ +h1, +.h1 { + @apply text-size-h1-sm/[1.25] md:text-size-h1/[1.25]; +} +h2, +.h2 { + @apply text-size-h2/[1.15]; +} +h3, +.h3 { + @apply text-size-h3/[1.2]; +} +h4, +.h4 { + @apply text-size-h4/[1.2]; +} +h5, +.h5 { + @apply text-size-h5/[1.2]; +} +h6, +.h6 { + @apply text-size-h6/[1.2]; +} + +/* Heading top & bottom margins */ +h1 { + @apply mb-[.3em]; +} +h2, h3, h4, h5, h6 { + @apply mb-[.3em] mt-[.6em]; +} + +/* +============================== +Links +============================== +*/ +a, .a { + color: var(--color-clr-secondary); + text-decoration: none; + transition: all 0.2s ease; + + .dark & { + color: var(--color-clr-darkmode-secondary); + } +} + +/* +============================== +Lists +============================== +*/ +/* Top-level lists */ +ul, ol { + /* Top & bottom margin */ + margin: 0.75rem 0; +} + +/* Nested lists */ +ul ul, +ol ol, +ul ol, +ol ul { + /* Top & bottom margin: same as list item margin */ + margin: 0; + /* Align level n+1 bullet with level n text */ + padding-inline-start: 1em; +} + +/* List items */ +ul li, ol li { + line-height: var(--tw-leading, var(--text-base--line-height)); + margin: 0.2rem 0; +} + +/* +============================== +Text +============================== +*/ +b, +strong { + @apply font-bold; +} + +/* +============================== +Borders used as horizontal separators +============================== +*/ +.hr-border-bottom { + @apply border-b-[3px] border-solid border-clr-light dark:border-clr-darkmode-border; +} + +/* +============================== +Spaced list + +A horizontal list of links with consistent spacing. +Supports wrapping on smaller screens. + +Usage: +
+ Link 1 + Link 2 +
+============================== +*/ +.spaced-list { + @apply flex flex-wrap items-center gap-5; +} diff --git a/website/themes/sambee/assets/css/buttons.css b/website/themes/sambee/assets/css/buttons.css new file mode 100755 index 0000000..9d9aede --- /dev/null +++ b/website/themes/sambee/assets/css/buttons.css @@ -0,0 +1,34 @@ +/* Button base styles */ +.btn { + @apply border-2 border-transparent inline-block rounded px-5 py-2 transition; +} +.btn-sm { + @apply rounded-sm px-4 py-1.5 text-sm; +} + +/* Button, background filled with secondary color */ +.btn-bg-secondary { + @apply bg-clr-secondary dark:bg-clr-darkmode-secondary text-white dark:text-clr-darkmode-text-dark; +} +/* Hover state for button with link */ +a.btn-bg-secondary { + @apply hover:bg-clr-secondary/80 dark:hover:bg-clr-darkmode-secondary/90; +} + +/* Button, background transparent */ +.btn-bg-transparent { + @apply bg-transparent text-clr-text-dark dark:text-clr-darkmode-text-dark; +} +/* Hover state for button with link */ +a.btn-bg-transparent { + @apply hover:bg-clr-secondary dark:hover:bg-clr-darkmode-secondary hover:text-white dark:hover:text-clr-darkmode-text-dark; +} + +/* Button, no background, border with secondary color */ +.btn-outline-secondary { + @apply border-clr-secondary text-clr-text-dark dark:text-clr-darkmode-text-dark; +} +/* Hover state for button with link */ +a.btn-outline-secondary { + @apply hover:text-clr-secondary dark:hover:text-clr-darkmode-secondary; +} diff --git a/website/themes/sambee/assets/css/code.css b/website/themes/sambee/assets/css/code.css new file mode 100644 index 0000000..e82f940 --- /dev/null +++ b/website/themes/sambee/assets/css/code.css @@ -0,0 +1,102 @@ +/* Code Blocks and Inline Code Styling */ + +/* Light mode code blocks */ +html:not(.dark) .content pre, +html:not(.dark) .content .highlight { + background-color: var(--color-clr-code-block-background); + color: var(--color-clr-text-dark); +} + +html:not(.dark) .content pre code { + color: inherit; +} + +/* Dark mode code blocks */ +html.dark .content pre, +html.dark .content .highlight { + background-color: var(--color-clr-darkmode-code-block-background); +} + +/* Inline code styling - matching code block backgrounds and colors */ +.content code:not(pre code) { + background-color: var(--color-clr-code-inline-background); + color: var(--color-clr-text-dark); + font-size: var(--code-font-size); + font-weight: normal; + padding: 0.125rem 0.375rem; +} + +html.dark .content code:not(pre code) { + background-color: var(--color-clr-darkmode-code-inline-background); + color: var(--color-clr-text-light); +} + +/* Code block font size, spacing, and overflow */ +.content pre { + font-size: var(--code-font-size); + padding: 1em; + margin: var(--content-block-margin) 0; + overflow-x: auto; +} + +/* Highlight container needs position relative for copy button */ +.content .highlight, +.content .code-block-wrapper { + position: relative; +} + +/* Code copy button */ +.code-copy-btn { + position: absolute; + top: 0.5rem; + right: 0.5rem; + padding: 0.375rem; + background-color: #e0e0e0; + border: none; + cursor: pointer; + opacity: 0; + transition: opacity 0.2s ease, background-color 0.2s ease; + color: var(--color-clr-text-dark); + display: flex; + align-items: center; + justify-content: center; +} + +/* Icon visibility - show copy icon by default, check icon when copied */ +.code-copy-btn .icon-check { + display: none; +} + +.code-copy-btn.copied .icon-copy { + display: none; +} + +.code-copy-btn.copied .icon-check { + display: block; +} + +.highlight:hover .code-copy-btn, +.code-block-wrapper:hover .code-copy-btn { + opacity: 1; +} + +.code-copy-btn:hover { + background-color: #d0d0d0; +} + +.code-copy-btn.copied { + color: var(--color-clr-primary); +} + +html.dark .code-copy-btn { + background-color: #4a4a4a; + color: var(--color-clr-text-light); +} + +html.dark .code-copy-btn:hover { + background-color: #5a5a5a; +} + +html.dark .code-copy-btn.copied { + color: var(--color-clr-primary); +} diff --git a/website/themes/sambee/assets/css/content.css b/website/themes/sambee/assets/css/content.css new file mode 100644 index 0000000..aa16ac7 --- /dev/null +++ b/website/themes/sambee/assets/css/content.css @@ -0,0 +1,215 @@ +/* + Content typography for blog posts + Complements base.css - only adds styles specific to .content area +*/ + +.content { + + /* + ============================== + Headings + ============================== + */ + + /* Top & bottom margins */ + h2, h3, h4, h5, h6 { + @apply mb-[.75em] mt-[1em]; + } + + /* + ============================== + Paragraphs + ============================== + */ + p { + @apply my-[var(--content-block-margin)]; + } + + /* + ============================== + Lists + ============================== + */ + ul { + list-style-type: disc; + padding-inline-start: 1.5em; + } + + ol { + list-style-type: decimal; + padding-inline-start: 1.5em; + } + + li { + margin: 0.25em 0; + padding-left: 0.25em; + } + + /* + ============================== + Blockquotes + ============================== + */ + blockquote { + border-left: 4px solid var(--color-clr-primary); + padding-left: 1em; + margin: var(--content-block-margin) 0; + font-style: italic; + color: var(--color-clr-text-light); + + .dark & { + color: var(--color-clr-darkmode-text-light); + } + + p:last-child { + margin-bottom: 0; + } + } + + /* + ============================== + Horizontal Rules + ============================== + */ + hr { + border: none; + border-top: 1px solid var(--color-clr-border); + margin: 2em 0; + + .dark & { + border-top-color: var(--color-clr-darkmode-border); + } + } + + /* + ============================== + Tables + ============================== + */ + table { + width: 100%; + border-collapse: collapse; + margin: var(--content-block-margin) 0; + font-size: 0.9375em; + overflow-x: auto; + display: block; + } + + th, + td { + border: 1px solid var(--color-clr-border); + padding: 0.5em 0.75em; + text-align: left; + vertical-align: top; + + .dark & { + border-color: var(--color-clr-darkmode-border); + } + } + + thead th { + background-color: var(--color-clr-light); + font-weight: 600; + + .dark & { + background-color: var(--color-clr-darkmode-light); + } + } + + /* + ============================== + Images & Figures + ============================== + */ + figure { + margin: var(--content-block-margin) 0; + } + + figcaption { + @apply mt-4 text-sm text-clr-text-light dark:text-clr-darkmode-text-light; + } + + /* + ============================== + Definition Lists + ============================== + */ + dl { + margin: var(--content-block-margin) 0; + } + + dt { + font-weight: 600; + margin-top: 1em; + } + + dd { + margin-left: 1.5em; + margin-top: 0.25em; + } + + /* + ============================== + Abbreviations + ============================== + */ + abbr[title] { + text-decoration: underline dotted; + cursor: help; + } + + /* + ============================== + Keyboard Input + ============================== + */ + kbd { + font-family: var(--font-mono, ui-monospace, monospace); + font-size: 0.875em; + padding: 0.125em 0.375em; + background-color: var(--color-clr-light); + border: 1px solid var(--color-clr-border); + border-radius: 0.25em; + + .dark & { + background-color: var(--color-clr-darkmode-light); + border-color: var(--color-clr-darkmode-border); + } + } + + /* + ============================== + Superscript & Subscript + ============================== + */ + sup, + sub { + font-size: 0.75em; + line-height: 0; + position: relative; + vertical-align: baseline; + } + + sup { + top: -0.5em; + } + + sub { + bottom: -0.25em; + } + + /* + ============================== + Mark (Highlighted Text) + ============================== + */ + mark { + background-color: rgba(0, 204, 158, 0.2); + padding: 0.1em 0.2em; + border-radius: 0.125em; + + .dark & { + background-color: rgba(0, 204, 158, 0.3); + } + } +} diff --git a/website/themes/sambee/assets/css/custom.css b/website/themes/sambee/assets/css/custom.css new file mode 100644 index 0000000..2c76da5 --- /dev/null +++ b/website/themes/sambee/assets/css/custom.css @@ -0,0 +1,198 @@ +.site-main { + @apply pb-12; +} + +.shell-section, +.docs-overview { + @apply container; +} + +.hero { + @apply grid gap-8 py-8 lg:grid-cols-[minmax(0,1.35fr)_minmax(280px,0.8fr)] lg:items-end; +} + +.hero-copy, +.hero-card, +.docs-card, +.docs-sidebar, +.docs-content { + @apply rounded-2xl border border-clr-light bg-white/85 shadow-sm; + + .dark & { + @apply border-clr-darkmode-border bg-clr-darkmode-light/80; + } +} + +.hero-copy, +.hero-card, +.docs-card, +.docs-content { + @apply p-6 lg:p-8; +} + +.hero-card { + @apply lg:translate-y-6; +} + +.eyebrow, +.docs-card-kicker { + @apply mb-3 inline-flex text-xs font-tertiary uppercase tracking-[0.18em] text-clr-primary; + + .dark & { + @apply text-clr-darkmode-primary; + } +} + +.lede { + @apply max-w-3xl text-lg leading-relaxed; +} + +.button-row { + @apply mt-6 flex flex-wrap gap-3; +} + +.hero-stats { + @apply mt-6 grid gap-3 sm:grid-cols-3; +} + +.stat-card { + @apply rounded-xl border border-clr-light bg-clr-light/70 px-4 py-4 text-center; + + .dark & { + @apply border-clr-darkmode-border bg-clr-darkmode-body/60; + } + + strong { + @apply block font-tertiary text-xl text-clr-text-dark; + + .dark & { + @apply text-clr-darkmode-text-dark; + } + } + + span { + @apply text-sm text-clr-text-light; + + .dark & { + @apply text-clr-darkmode-text-light; + } + } +} + +.docs-header { + @apply mb-8 max-w-3xl; +} + +.book-grid { + @apply grid gap-4 md:grid-cols-2 xl:grid-cols-3; +} + +.docs-card { + @apply transition-transform duration-200 hover:-translate-y-1; + + h2, h3 { + @apply mb-3; + } + + p { + @apply my-0 text-clr-text-light; + + .dark & { + @apply text-clr-darkmode-text-light; + } + } +} + +.docs-shell { + @apply container grid gap-8 lg:grid-cols-[280px_minmax(0,1fr)] lg:items-start; +} + +.docs-sidebar { + @apply p-5 lg:sticky; + top: calc(var(--header-height) + 1.5rem); +} + +.docs-sidebar-title { + @apply mb-4 text-xl font-secondary text-clr-text-dark; + + .dark & { + @apply text-clr-darkmode-text-dark; + } +} + +.docs-nav-group + .docs-nav-group { + @apply mt-6; +} + +.docs-nav-group strong { + @apply text-sm font-tertiary uppercase tracking-[0.14em] text-clr-text-light; + + .dark & { + @apply text-clr-darkmode-text-light; + } +} + +.docs-nav-list { + @apply mt-3 list-none p-0; +} + +.docs-nav-list li + li { + @apply mt-2; +} + +.docs-sidebar-link { + @apply text-sm text-clr-text transition-colors duration-150 hover:text-clr-secondary; + + .dark & { + @apply text-clr-darkmode-text hover:text-clr-darkmode-secondary; + } +} + +.docs-sidebar-link-current { + @apply font-bold text-clr-secondary; + + .dark & { + @apply text-clr-darkmode-secondary; + } +} + +.docs-meta { + @apply mb-5 flex flex-wrap gap-2; +} + +.docs-meta-chip, +.version-chip { + @apply inline-flex items-center rounded-full border border-clr-light px-3 py-1 text-sm; + + .dark & { + @apply border-clr-darkmode-border; + } +} + +.version-switcher { + @apply mb-5 flex flex-wrap items-center gap-2; +} + +.version-switcher-label { + @apply mr-1 text-sm font-tertiary uppercase tracking-[0.14em] text-clr-text-light; + + .dark & { + @apply text-clr-darkmode-text-light; + } +} + +.version-chip { + @apply text-clr-text hover:border-clr-secondary hover:text-clr-secondary; + + .dark & { + @apply text-clr-darkmode-text hover:border-clr-darkmode-secondary hover:text-clr-darkmode-secondary; + } +} + +.version-chip-current { + @apply border-clr-secondary bg-clr-secondary/10 font-bold text-clr-secondary; + + .dark & { + @apply border-clr-darkmode-secondary bg-clr-darkmode-secondary/15 text-clr-darkmode-secondary; + } +} diff --git a/website/themes/sambee/assets/css/footer.css b/website/themes/sambee/assets/css/footer.css new file mode 100644 index 0000000..9d1a101 --- /dev/null +++ b/website/themes/sambee/assets/css/footer.css @@ -0,0 +1,63 @@ +/* ============================================ + Footer Styles + ============================================ + + Single-row footer with three sections: + - Left: Copyright + - Center: Social icons + - Right: Legal links + + Mobile: Stacked vertically, center-aligned + Desktop: Single row with space-between + + Uses same .container as header for consistent content width. + + ============================================ */ + +.footer { + @apply bg-clr-light dark:bg-clr-darkmode-light; + @apply mt-6 lg:mt-10; + @apply py-6; +} + +/* Footer bar: flex container for the three sections */ +.footer-bar { + @apply flex flex-col items-center gap-4; + @apply text-sm; + + /* Desktop: single row */ + @media (min-width: theme(--breakpoint-lg)) { + @apply flex-row justify-between gap-6; + } +} + +/* Copyright section */ +.footer-copyright { + @apply text-clr-text dark:text-clr-darkmode-text; + @apply order-2 lg:order-1; +} + +/* Social icons section */ +.footer-social { + @apply flex items-center gap-4; + @apply order-1 lg:order-2; +} + +.footer-social-link { + @apply text-clr-text dark:text-clr-darkmode-text; + @apply hover:text-clr-primary dark:hover:text-clr-darkmode-primary; + @apply transition-colors duration-200; + + /* Ensure adequate touch target */ + @apply p-1; +} + +/* Legal links section - extends .dot-list from components.css */ +.footer-links { + @apply order-3; +} + +.footer-link { + @apply text-clr-text dark:text-clr-darkmode-text; + @apply hover:text-clr-secondary dark:hover:text-clr-darkmode-secondary; +} diff --git a/website/themes/sambee/assets/css/main.css b/website/themes/sambee/assets/css/main.css new file mode 100755 index 0000000..ec102fd --- /dev/null +++ b/website/themes/sambee/assets/css/main.css @@ -0,0 +1,54 @@ +@import "tailwindcss"; + /* + Default breakpoints (https://tailwindcss.com/docs/responsive-design) + sm: 40rem (640px) + md: 48rem (768px) + lg: 64rem (1024px) + xl: 80rem (1280px) + 2xl: 96rem (1536px) + */ +@plugin 'tailwind-bootstrap-grid' { + /* Limit the grid width (= space available for content) to: + 1-column mode: 540px + 2-column mode: 900px + 3-column mode and up: 1131px (--article-container-width defined in base.css) + */ + container_max_widths: + 'sm', '540px', 'md', '900px', 'lg', '1131px', 'xl', '1131px', '2xl', '1131px'; +} + +/* Auto-generated by Hugo, showing Tailwind which CSS utility classes are used. + Only Tailwind classes listed in this JSON will make it into the final style.css. */ +@source "../../../../hugo_stats.json"; + +/* Define a Tailwind variant "dark" that lets us write "dark:" utilities */ +@custom-variant dark (&:where(.dark, .dark *)); + +/* Auto-generated theme from "data/theme.json" */ +@import "./generated-theme.css"; + +@layer base { + @import "./base.css"; +} + +@layer components { + @import "./content.css"; + @import "./buttons.css"; + @import "./article.css"; + @import "./posts-grid.css"; + @import "./search.css"; + @import "./navigation.css"; + @import "./footer.css"; +} + +/* TODO: continue here with cleanup */ +@import "modal.css"; + +@import "module-overrides.css"; +@import "custom.css"; + +/* Code styling */ +@import "code.css"; + +/* Syntax highlighting (Chroma) */ +@import "syntax.css"; diff --git a/website/themes/sambee/assets/css/modal.css b/website/themes/sambee/assets/css/modal.css new file mode 100644 index 0000000..64b5597 --- /dev/null +++ b/website/themes/sambee/assets/css/modal.css @@ -0,0 +1 @@ +/* Reserved for future modal-specific overrides. */ diff --git a/website/themes/sambee/assets/css/module-overrides.css b/website/themes/sambee/assets/css/module-overrides.css new file mode 100644 index 0000000..f62f088 --- /dev/null +++ b/website/themes/sambee/assets/css/module-overrides.css @@ -0,0 +1,4 @@ +/* table of contents */ +.table-of-content { + @apply overflow-hidden rounded; +} diff --git a/website/themes/sambee/assets/css/navigation.css b/website/themes/sambee/assets/css/navigation.css new file mode 100755 index 0000000..43f5fe5 --- /dev/null +++ b/website/themes/sambee/assets/css/navigation.css @@ -0,0 +1,654 @@ +/* ============================================ + Navigation Styles + ============================================ + + This file contains all styling for the site navigation system. + + ARCHITECTURE OVERVIEW: + ---------------------- + The navigation uses a mobile-first approach with two distinct modes: + + 1. MOBILE (< lg breakpoint): + - Hamburger menu button toggles full-screen overlay + - Menu slides in from right via CSS transform + - Submenus expand/collapse via JavaScript toggle (accordion-style) + - Body scroll is locked when menu is open + + 2. DESKTOP (>= lg breakpoint): + - Horizontal menu bar with items inline + - Submenus appear as dropdowns on hover + - Uses opacity/visibility for smooth fade-in effect + + MENU STRUCTURE (up to 3 levels): + -------------------------------- + .nav-menu - Container for all menu items + .nav-item - L1: Top-level menu item + .nav-link - L1: Clickable link text + .nav-submenu-toggle - Mobile: Button to expand/collapse submenu + .nav-submenu - L2: Dropdown container + .nav-submenu-group - L2: Group with optional heading + .nav-submenu-heading - L2: Group heading (may be link or text) + .nav-submenu-items - L3: Container for third-level items + .nav-submenu-item - L3: Individual item + .nav-submenu-link - L3: Clickable link + + CSS CUSTOM PROPERTIES: + ---------------------- + --nav-transition-duration: Animation speed for menu open/close + --nav-submenu-indent: Left indent for nested submenu levels + --nav-hamburger-size: Width of hamburger icon bars + --nav-hamburger-bar-height: Thickness of hamburger icon bars + --header-height: Set by JS, used to position mobile menu below header + + ============================================ */ + +:root { + --nav-transition-duration: 0.3s; + --nav-submenu-indent: 0rem; + --nav-hamburger-size: 24px; + --nav-hamburger-bar-height: 2px; +} + +/* ============================================ + Header & Nav Bar + ============================================ + + The header contains the navigation bar and is made sticky. + The nav-bar uses flexbox to arrange logo, menu, and actions. + + Layout order (mobile): Logo | Actions | Hamburger + Layout order (desktop): Logo | Menu | Actions + + ============================================ */ + +.header { + /* Background matches page body for seamless look */ + @apply bg-clr-body dark:bg-clr-darkmode-body; + /* Vertical padding and bottom margin for spacing from content */ + @apply pt-4 pb-2 mb-6 lg:mb-10; +} + +.header-shell { + /* Sticky positioning keeps header visible on scroll */ + @apply sticky top-0 z-30; +} + +.nav-bar { + /* Flex container for logo, menu, and actions */ + @apply relative flex flex-wrap items-center justify-between; +} + +.nav-logo { + /* First in order, slight vertical adjustment for optical alignment */ + @apply order-0 translate-y-0.5 lg:-translate-y-0.5; +} + +.nav-logo-link { + @apply text-clr-text-dark dark:text-clr-darkmode-text-dark text-xl font-bold; +} + +.nav-logo-link img { + /* Constrain logo image to container */ + @apply max-h-full max-w-full; +} + +.nav-actions { + /* Contains theme switcher, search button, etc. + Mobile: order-1 (after logo), Desktop: order-2 (after menu) */ + @apply order-1 ml-auto flex items-center md:order-2 lg:ml-0; +} + +/* ============================================ + Hamburger Toggle Button + ============================================ + + Three-bar hamburger icon that animates to an X when active. + Uses ::before and ::after pseudo-elements for the top and bottom bars. + The middle bar fades out while the outer bars rotate into an X. + + Only visible on mobile (hidden at lg breakpoint). + + ============================================ */ + +.nav-toggle { + @apply relative flex items-center justify-center cursor-pointer; + @apply text-clr-text-dark dark:text-clr-darkmode-text-dark; + /* Hidden on desktop */ + @apply lg:hidden; + /* Placed after actions in flex order */ + order: 3; + /* Touch-friendly tap target size */ + width: 40px; + height: 40px; + background: transparent; + border: none; + padding: 0; +} + +/* All three bars share these styles */ +.nav-toggle-bar, +.nav-toggle-bar::before, +.nav-toggle-bar::after { + @apply bg-current; + display: block; + width: var(--nav-hamburger-size); + height: var(--nav-hamburger-bar-height); + border-radius: 1px; + transition: transform var(--nav-transition-duration) ease, + background-color var(--nav-transition-duration) ease; +} + +.nav-toggle-bar { + /* Middle bar - positioned relatively for pseudo-element positioning */ + position: relative; +} + +.nav-toggle-bar::before, +.nav-toggle-bar::after { + content: ''; + position: absolute; + left: 0; +} + +.nav-toggle-bar::before { + /* Top bar - positioned above middle */ + top: -7px; +} + +.nav-toggle-bar::after { + /* Bottom bar - positioned below middle */ + top: 7px; +} + +/* ACTIVE STATE: Hamburger transforms to X + - Middle bar becomes transparent + - Top bar rotates 45° clockwise and moves to center + - Bottom bar rotates 45° counter-clockwise and moves to center */ +.nav-toggle.active .nav-toggle-bar { + background: transparent; +} + +.nav-toggle.active .nav-toggle-bar::before { + top: 0; + transform: rotate(45deg); +} + +.nav-toggle.active .nav-toggle-bar::after { + top: 0; + transform: rotate(-45deg); +} + +/* Accessibility: Respect user's motion preferences */ +@media (prefers-reduced-motion: reduce) { + .nav-toggle-bar, + .nav-toggle-bar::before, + .nav-toggle-bar::after { + transition: none; + } +} + +/* Desktop: Hide hamburger button entirely */ +@media (min-width: theme(--breakpoint-lg)) { + .nav-toggle { + display: none; + } +} + +/* ============================================ + Mobile Menu (Full-screen overlay) + ============================================ + + On mobile, the menu is a fixed overlay that slides in from the right. + - Positioned below the header (using --header-height CSS variable) + - Full viewport height minus header + - Scrollable if content overflows + + The menu is hidden by default (translateX: 100%) and revealed + when body has .nav-menu-open class. + + ============================================ */ + +.nav-menu { + /* Fixed positioning for overlay effect */ + @apply fixed bg-clr-body dark:bg-clr-darkmode-body; + @apply flex flex-col overflow-y-auto; + /* Position below header - --header-height set by JavaScript */ + top: var(--header-height); + left: 0; + right: 0; + bottom: 0; + padding: 1rem 0; + /* Hidden off-screen to the right */ + transform: translateX(100%); + transition: transform var(--nav-transition-duration) ease; + z-index: 40; + list-style: none; + margin: 0; +} + +/* Menu visible state - slide in from right */ +body.nav-menu-open .nav-menu { + transform: translateX(0); +} + +/* Prevent body scroll when mobile menu is open */ +body.nav-menu-open { + overflow: hidden; +} + +/* Accessibility: Respect user's motion preferences */ +@media (prefers-reduced-motion: reduce) { + .nav-menu { + transition: none; + } +} + +/* DESKTOP: Transform to horizontal inline menu + - No longer fixed/overlay, becomes inline in nav-bar + - Horizontal layout with flexbox + - Positioned between logo and actions */ +@media (min-width: theme(--breakpoint-lg)) { + .nav-menu { + @apply static flex flex-row items-center; + /* Reset all the mobile overlay positioning */ + position: static; + top: auto; + left: auto; + right: auto; + bottom: auto; + transform: none; + overflow: visible; + padding: 0; + background: transparent; + /* Small gap between menu items */ + gap: 0.25rem; + /* Placed after logo in flex order */ + order: 1; + /* Gap between logo and first menu item */ + margin-left: 2.5rem; + /* Push actions (theme switcher, search) to the right */ + margin-right: auto; + } + + /* Restore body scroll on desktop */ + body.nav-menu-open { + overflow: auto; + } +} + +/* ============================================ + Nav Items (L1 - Top Level) + ============================================ + + Top-level navigation items. Each contains a .nav-link and + optionally a .nav-submenu for dropdown content. + + Mobile: Full-width with bottom border separators + Desktop: Inline items without borders + + ============================================ */ + +.nav-item { + @apply relative; + /* Mobile: Visual separator between items */ + border-bottom: 1px solid theme('colors.clr-light'); +} + +.dark .nav-item { + border-bottom-color: theme('colors.clr-darkmode-border'); +} + +/* Desktop: Remove borders */ +@media (min-width: theme(--breakpoint-lg)) { + .nav-item { + border-bottom: none; + } +} + +/* Nav Link: The clickable text for L1 items + - elements (actual links): secondary color on hover + - elements (dropdown triggers without URL): primary color on hover */ +.nav-link { + @apply block font-secondary font-normal text-lg; + @apply text-clr-text-dark dark:text-clr-darkmode-text-dark; + /* Generous padding for touch targets */ + padding: 1rem 1.25rem; + /* Extra right padding to make room for submenu toggle button */ + padding-right: 3.5rem; + transition: color 0.2s ease; + + /* Anchor links: secondary color on hover */ + &:is(a):hover { + @apply text-clr-secondary dark:text-clr-darkmode-secondary; + } + + /* Non-link spans (dropdown triggers): primary color on hover */ + &:is(span):hover { + @apply text-clr-primary dark:text-clr-darkmode-primary; + } +} + +/* Items without children don't need extra right padding */ +.nav-item:not(.has-children) .nav-link { + padding-right: 1.25rem; +} + +/* Active state: Highlight current section/page */ +.nav-item.active > .nav-link, +.nav-link.active { + @apply text-clr-secondary dark:text-clr-darkmode-secondary; +} + +/* Desktop nav link adjustments */ +@media (min-width: theme(--breakpoint-lg)) { + .nav-link { + /* Slightly larger font for desktop */ + font-size: 1.25rem; + /* Compact padding for horizontal layout */ + padding: 0.75rem 0.75rem; + } + + /* Reset right padding for all items */ + .nav-item:not(.has-children) .nav-link, + .nav-item.has-children .nav-link { + padding-right: 0.75rem; + } + + /* Items with dropdowns need space for chevron indicator */ + .nav-item.has-children > .nav-link { + padding-right: 1.5rem; + } + + /* Desktop chevron indicator: Small downward-pointing triangle + Indicates this item has a dropdown submenu */ + .nav-item.has-children > .nav-link::after { + content: ''; + position: absolute; + right: 0.25rem; + top: 50%; + transform: translateY(-50%); + /* CSS triangle using borders */ + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid currentColor; + } +} + +/* ============================================ + Submenu Toggle Button (Mobile only) + ============================================ + + On mobile, a separate button to expand/collapse submenus. + Positioned absolutely on the right side of the nav-item. + Contains a chevron icon that rotates when submenu is open. + + Hidden on desktop where hover handles submenu visibility. + + ============================================ */ + +.nav-submenu-toggle { + @apply absolute right-0 top-0 flex items-center justify-center; + @apply text-clr-text-dark dark:text-clr-darkmode-text-dark; + /* Large tap target for mobile usability */ + width: 56px; + height: 56px; + background: transparent; + border: none; + cursor: pointer; +} + +.nav-chevron { + @apply w-5 h-5 fill-current; + transition: transform var(--nav-transition-duration) ease; +} + +/* Rotate chevron 180° when submenu is open */ +.has-children.submenu-open > .nav-submenu-toggle .nav-chevron { + transform: rotate(180deg); +} + +/* Desktop: Hide toggle button (hover handles visibility) */ +@media (min-width: theme(--breakpoint-lg)) { + .nav-submenu-toggle { + display: none; + } +} + +/* ============================================ + Submenus (Dropdown) - L2 and L3 + ============================================ + + MOBILE: Accordion-style expand/collapse + - max-height animation for smooth open/close + - Indented with --nav-submenu-indent + - JavaScript toggles .submenu-open class + + DESKTOP: Hover-triggered dropdown + - Absolutely positioned below parent + - Fade in with opacity/transform animation + - Width adapts to content (min-width to max-width range) + + ============================================ */ + +/* Mobile: Collapsed by default, expands via max-height */ +.nav-submenu { + @apply bg-clr-body dark:bg-clr-darkmode-body; + /* Hidden by default */ + max-height: 0; + overflow: hidden; + transition: max-height var(--nav-transition-duration) ease; + /* Indent to show hierarchy */ + padding-left: var(--nav-submenu-indent); + list-style: none; + margin: 0; +} + +/* Mobile: Expanded state */ +.nav-item.submenu-open > .nav-submenu { + /* Large value ensures content fits; actual height is content-based */ + max-height: 1000px; +} + +/* Submenu Group: Container for a heading + its child items + Used when L2 items have their own L3 children */ +.nav-submenu-group { + @apply relative; + margin-top: 0.5rem; +} + +.nav-submenu-group:first-child { + margin-top: 0; +} + +/* Submenu Heading: Group title at L2 level + Can be plain text or a clickable link */ +.nav-submenu-heading { + @apply block font-bold; + @apply text-clr-text-dark dark:text-clr-darkmode-text-dark; + padding: 0.75rem 1.25rem; +} + +/* Clickable heading variant */ +.nav-submenu-heading--link { + @apply hover:text-clr-secondary dark:hover:text-clr-darkmode-secondary; + transition: color 0.2s ease; + text-decoration: none; +} + +.nav-submenu-heading--link.active { + @apply text-clr-secondary dark:text-clr-darkmode-secondary; +} + +/* L3 Items Container: Third level navigation items */ +.nav-submenu-items { + list-style: none; + margin: 0; + padding: 0; + /* Further indent for L3 items */ + padding-left: var(--nav-submenu-indent); +} + +/* Individual L3 item */ +.nav-submenu-item { + @apply relative; + margin: 0.2rem 0; +} + +/* L3 link styling */ +.nav-submenu-link { + @apply block; + @apply text-clr-text-dark dark:text-clr-darkmode-text-dark; + @apply hover:text-clr-secondary dark:hover:text-clr-darkmode-secondary; + padding: 0.5rem 1.25rem; + transition: color 0.2s ease; +} + +.nav-submenu-link.active { + @apply text-clr-secondary dark:text-clr-darkmode-secondary font-bold; +} + +/* Accessibility: Respect user's motion preferences */ +@media (prefers-reduced-motion: reduce) { + .nav-submenu { + transition: none; + } +} + +/* DESKTOP: Hover-triggered dropdown with fade animation */ +@media (min-width: theme(--breakpoint-lg)) { + .nav-submenu { + /* Position dropdown below parent nav-item */ + @apply absolute left-0 top-full; + @apply shadow-lg; + /* + * CONTENT-AWARE SIZING: + * - width: max-content - sizes to fit longest item + * - min-width: 220px - ensures dropdown isn't too narrow + * - max-width: 500px - caps growth for very long items + */ + width: max-content; + min-width: 220px; + max-width: 500px; + /* Inner whitespace: container padding handles border-to-content spacing, + individual items use minimal horizontal padding */ + padding: 1rem; + /* Override mobile max-height restriction */ + max-height: none; + /* Hidden by default - revealed on hover */ + opacity: 0; + visibility: hidden; + /* Subtle upward offset that animates to 0 on show */ + transform: translateY(-8px); + transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s; + /* Subtle border for definition */ + border: 1px solid theme('colors.clr-light'); + } + + .dark .nav-submenu { + border-color: theme('colors.clr-darkmode-border'); + } + + /* Show dropdown on hover or keyboard focus */ + .nav-item:hover > .nav-submenu, + .nav-item:focus-within > .nav-submenu { + opacity: 1; + visibility: visible; + transform: translateY(0); + } + + /* Desktop submenu group styling: Add separators between groups */ + .nav-submenu-group { + margin-top: 0.75rem; + padding-top: 0.5rem; + border-top: 1px solid theme('colors.clr-light'); + } + + .dark .nav-submenu-group { + border-top-color: theme('colors.clr-darkmode-border'); + } + + .nav-submenu-group:first-child { + margin-top: 0.2rem; + padding-top: 0; + border-top: none; + } + + /* Desktop heading style: Smaller, uppercase label + Padding matches .nav-submenu-link for consistent alignment */ + .nav-submenu-heading { + padding: 0.4rem 1rem; + letter-spacing: 0.05em; + } + + /* Desktop L3 items: Indent under headings */ + .nav-submenu-items { + padding-left: var(--nav-submenu-indent); + } + + /* Tighter padding for desktop dropdown items */ + .nav-submenu-link { + padding: 0.4rem 1rem; + } +} + +/* ============================================ + Theme Switcher + ============================================ + + Toggle switch for light/dark mode. + Uses a hidden checkbox with styled label for the switch track, + and a span for the circular toggle. + + ============================================ */ + +.theme-switcher { + @apply inline-flex items-center; + /* Minimum tap target size */ + min-width: 2.5rem; + min-height: 1.5rem; +} + +/* Switch track (background) */ +.theme-switcher label { + @apply bg-clr-border relative inline-block h-4 cursor-pointer rounded-2xl; + /* Narrower on mobile, wider on desktop */ + @apply w-6 lg:w-10; +} + +/* Hidden checkbox - accessibility: still in DOM for screen readers */ +.theme-switcher input { + @apply absolute opacity-0; +} + +/* Toggle circle */ +.theme-switcher span { + @apply bg-clr-text-dark dark:bg-clr-darkmode-text-dark; + @apply absolute -top-1 left-0 flex h-6 w-6 items-center justify-center rounded-full; + @apply transition-all duration-300; +} + +/* Checked state: Move toggle to the right */ +.theme-switcher input:checked + label span { + @apply lg:left-4; +} + +/* ============================================ + Search Button + ============================================ + + Button in nav-actions to open search modal. + Has a right border as visual separator from theme switcher. + + ============================================ */ + +.nav-search-button { + @apply border-clr-border dark:border-clr-darkmode-border; + @apply text-clr-text-dark dark:text-clr-darkmode-text-dark; + @apply hover:text-clr-primary dark:hover:text-clr-darkmode-primary; + @apply mr-5 inline-flex items-center justify-center border-r pr-5 text-xl cursor-pointer; + /* Minimum touch target size */ + min-width: 1.25rem; + min-height: 1.25rem; +} diff --git a/website/themes/sambee/assets/css/posts-grid.css b/website/themes/sambee/assets/css/posts-grid.css new file mode 100644 index 0000000..394c203 --- /dev/null +++ b/website/themes/sambee/assets/css/posts-grid.css @@ -0,0 +1,110 @@ +/* Combined styles: featured and regular posts */ +.posts-grid__item, +.posts-grid__featured { + + /* Title */ + h3 { + color: var(--color-clr-text-dark); + font-family: var(--font-secondary); + + /* Dark mode */ + .dark & { + color: var(--color-clr-darkmode-text-dark); + } + } + + /* Excerpt */ + .post-excerpt { + color: var(--color-clr-text); + + /* Dark mode */ + .dark & { + color: var(--color-clr-darkmode-text); + } + } +} + +/* Featured posts */ +.posts-grid__featured { + + /* Title */ + h3 { + margin-top: 0; + } + /* Latest post: hidden by default */ + .post-latest { + display: none; + } +} + +/* Featured post on larger screens */ +@media (min-width: theme(--breakpoint-md)) { + + .posts-grid__featured { + + /* Grid */ + .featured-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } + + /* Title */ + h3 { + font-size: var(--text-size-h1); + } + + /* Image and content */ + .featured-image { + grid-column: span 1; + } + .featured-content { + grid-column: span 1; + padding-right: 0; + } + .featured-image img { + height: 100%; + } + + /* Latest post: show */ + .post-latest { + display: block; + } + } +} + +/* Regular posts (non-featured) */ +.posts-grid__item { + /* Title */ + h3 { + font-size: 1.5rem; + } + + /* Transition */ + transition: all 0.3s ease; + + /* Hover */ + &:hover { + transform: translateY(-4px); + } +} + +/* Highlight underline: primary color */ +.highlight-underline-primary { + height: 4px; + background-color: color-mix(in srgb, var(--color-clr-primary) 30%, transparent); + + .dark & { + color: var(--color-clr-darkmode-primary) 30%; + } +} + +/* Highlight underline: secondary color */ +.highlight-underline-secondary { + height: 2px; + background-color: color-mix(in srgb, var(--color-clr-secondary) 60%, transparent); + + .dark & { + color: var(--color-clr-darkmode-secondary) 50%; + } +} diff --git a/website/themes/sambee/assets/css/search.css b/website/themes/sambee/assets/css/search.css new file mode 100644 index 0000000..f08e4b5 --- /dev/null +++ b/website/themes/sambee/assets/css/search.css @@ -0,0 +1,357 @@ +/* + * Search Modal Styles + * + * Custom search UI with Pagefind backend. + * Matches theme design language. + */ + +/* Modal overlay and container */ +.search-modal { + position: fixed; + inset: 0; + z-index: 9999; + opacity: 0; + visibility: hidden; + transition: opacity 0.15s ease, visibility 0.15s ease; + + &.show { + opacity: 1; + visibility: visible; + } +} + +.search-modal-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(2px); +} + +/* Search wrapper */ +.search-wrapper { + position: relative; + z-index: 1; + width: 660px; + max-width: 96%; + max-height: calc(100vh - 120px); + margin: 60px auto; + background-color: var(--color-clr-body, #fff); + border-radius: 8px; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + display: flex; + flex-direction: column; + overflow: hidden; + + .dark & { + background-color: var(--color-clr-darkmode-body, #1c1c1c); + } + + @media (max-width: theme(--breakpoint-md)) { + margin: 20px auto; + max-height: calc(100vh - 40px); + } +} + +/* Search header with input */ +.search-wrapper-header { + position: relative; + padding: 16px; + border-bottom: 1px solid var(--color-clr-border, #e5e5e5); + + .dark & { + border-color: var(--color-clr-darkmode-border, #3e3e3e); + } + + label { + position: absolute; + left: 24px; + top: 50%; + transform: translateY(-50%); + color: var(--color-clr-text-light, #666); + display: flex; + align-items: center; + cursor: text; + } + + .search-icon { + width: 18px; + height: 18px; + } + + .search-reset { + cursor: pointer; + color: var(--color-clr-text-light, #666); + transition: color 0.15s ease; + + &:hover { + color: var(--color-clr-text, #333); + + .dark & { + color: var(--color-clr-darkmode-text, #ccc); + } + } + } + + input { + width: 100%; + padding: 12px 16px 12px 44px; + font-size: 16px; + border: 1px solid var(--color-clr-border, #e5e5e5); + border-radius: 6px; + background-color: var(--color-clr-body, #fff); + color: var(--color-clr-text, #333); + outline: none; + transition: border-color 0.15s ease, box-shadow 0.15s ease; + + .dark & { + background-color: var(--color-clr-darkmode-body, #1c1c1c); + border-color: var(--color-clr-darkmode-border, #3e3e3e); + color: var(--color-clr-darkmode-text, #ccc); + } + + &:focus { + border-color: var(--color-clr-primary, #00CC9E); + box-shadow: 0 0 0 3px rgba(0, 204, 158, 0.15); + } + + &::placeholder { + color: var(--color-clr-text-light, #999); + } + } +} + +/* Search body with results */ +.search-wrapper-body { + flex: 1; + overflow-y: auto; + padding: 16px; + min-height: 200px; + max-height: calc(100vh - 280px); + + @media (max-width: theme(--breakpoint-md)) { + max-height: calc(100vh - 140px); + } +} + +/* Empty state */ +.search-empty { + display: flex; + align-items: center; + justify-content: center; + height: 150px; + color: var(--color-clr-text-light, #666); + text-align: center; + + &.hidden { + display: none; + } + + .dark & { + color: var(--color-clr-darkmode-text-light, #909090); + } + + p { + margin: 0; + } + + strong { + color: var(--color-clr-text, #333); + + .dark & { + color: var(--color-clr-darkmode-text, #ccc); + } + } +} + +/* Search results */ +.search-results { + display: flex; + flex-direction: column; + gap: 8px; +} + +.search-result-item { + display: flex; + gap: 12px; + padding: 12px; + border: 1px solid var(--color-clr-border, #e5e5e5); + border-radius: 6px; + text-decoration: none; + color: inherit; + transition: border-color 0.15s ease, background-color 0.15s ease; + + .dark & { + border-color: var(--color-clr-darkmode-border, #3e3e3e); + } + + &:hover, + &.selected { + border-color: var(--color-clr-primary, #00CC9E); + background-color: rgba(0, 204, 158, 0.05); + + .dark & { + background-color: rgba(0, 204, 158, 0.1); + } + } + + &.selected { + outline: 2px solid var(--color-clr-primary, #00CC9E); + outline-offset: -2px; + } +} + +/* Result image */ +.search-result-image { + flex-shrink: 0; + width: 80px; + height: 54px; + border-radius: 4px; + overflow: hidden; + background-color: var(--color-clr-light, #f4f4f4); + + .dark & { + background-color: var(--color-clr-darkmode-light, #222); + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + @media (max-width: theme(--breakpoint-md)) { + width: 60px; + height: 40px; + } +} + +/* Result body */ +.search-result-body { + flex: 1; + min-width: 0; +} + +.search-result-title { + font-weight: 600; + font-size: 15px; + color: var(--color-clr-text, #333); + margin-bottom: 4px; + line-height: 1.3; + + .dark & { + color: var(--color-clr-darkmode-text-dark, #fff); + } +} + +.search-result-excerpt { + font-size: 13px; + line-height: 1.5; + color: var(--color-clr-text-light, #666); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + + .dark & { + color: var(--color-clr-darkmode-text-light, #909090); + } + + /* Highlight marks in excerpts */ + mark { + background-color: rgba(0, 204, 158, 0.3); + color: inherit; + padding: 1px 2px; + border-radius: 2px; + } +} + +/* Search footer */ +.search-wrapper-footer { + padding: 8px 16px; + font-size: 12px; + color: var(--color-clr-text-light, #999); + border-top: 1px solid var(--color-clr-border, #e5e5e5); + display: flex; + gap: 16px; + user-select: none; + + .dark & { + border-color: var(--color-clr-darkmode-border, #3e3e3e); + } + + @media (max-width: theme(--breakpoint-md)) { + display: none; + } + + kbd { + display: inline-block; + padding: 2px 6px; + font-size: 11px; + font-family: inherit; + background-color: var(--color-clr-light, #f4f4f4); + border-radius: 4px; + margin-right: 4px; + + .dark & { + background-color: var(--color-clr-darkmode-light, #333); + color: var(--color-clr-darkmode-text, #ccc); + } + } + + [data-close-search] { + margin-left: auto; + cursor: pointer; + + &:hover { + color: var(--color-clr-text, #666); + + .dark & { + color: var(--color-clr-darkmode-text, #ccc); + } + } + } +} + +.search-info { + flex: 1; + text-align: right; + + em { + font-style: normal; + font-weight: 500; + color: var(--color-clr-text, #333); + + .dark & { + color: var(--color-clr-darkmode-text, #ccc); + } + } +} + +/* Load more button */ +.search-load-more { + display: block; + width: 100%; + padding: 12px 16px; + margin-top: 8px; + font-size: 14px; + font-weight: 500; + color: var(--color-clr-primary, #00CC9E); + background-color: transparent; + border: 1px dashed var(--color-clr-border, #e5e5e5); + border-radius: 6px; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease; + + &:hover { + background-color: rgba(0, 204, 158, 0.05); + border-color: var(--color-clr-primary, #00CC9E); + + .dark & { + background-color: rgba(0, 204, 158, 0.1); + } + } + + .dark & { + border-color: var(--color-clr-darkmode-border, #3e3e3e); + } +} diff --git a/website/themes/sambee/assets/css/syntax.css b/website/themes/sambee/assets/css/syntax.css new file mode 100644 index 0000000..bd63040 --- /dev/null +++ b/website/themes/sambee/assets/css/syntax.css @@ -0,0 +1 @@ +/* Chroma syntax highlighting can be added here when code-heavy pages are migrated. */ diff --git a/website/themes/sambee/assets/js/main.js b/website/themes/sambee/assets/js/main.js new file mode 100755 index 0000000..33b76d9 --- /dev/null +++ b/website/themes/sambee/assets/js/main.js @@ -0,0 +1,5 @@ +// main script +(function () { + "use strict"; + // Empty placeholder for future scripts +})(); diff --git a/website/themes/sambee/assets/js/navigation.js b/website/themes/sambee/assets/js/navigation.js new file mode 100644 index 0000000..84ce329 --- /dev/null +++ b/website/themes/sambee/assets/js/navigation.js @@ -0,0 +1,156 @@ +/** + * Navigation Menu Controller + * Handles mobile menu toggle and submenu interactions + */ +(function () { + 'use strict'; + + const SELECTORS = { + toggle: '.nav-toggle', + menu: '.nav-menu', + submenuToggle: '.nav-submenu-toggle', + hasChildren: '.has-children' + }; + + const CLASSES = { + menuOpen: 'nav-menu-open', + submenuOpen: 'submenu-open', + active: 'active' + }; + + // Get lg breakpoint from CSS custom property (set in navigation.css) + // Handles both px and rem values; fallback to 1024px if not defined + function getBreakpointDesktop() { + const value = getComputedStyle(document.documentElement) + .getPropertyValue('--breakpoint-lg') + .trim(); + + if (!value) return 1024; + + // Handle rem values by converting to pixels + if (value.endsWith('rem')) { + const remValue = parseFloat(value); + const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize); + return remValue * rootFontSize; + } + + // Handle px values or raw numbers + return parseInt(value, 10) || 1024; + } + + let menuOpen = false; + + /** + * Measure the header height and set it as a CSS variable + * so the mobile menu is positioned directly below the header + */ + function updateHeaderHeight() { + const header = document.querySelector('.header'); + if (header) { + const height = header.offsetHeight; + document.documentElement.style.setProperty('--header-height', `${height}px`); + } + } + + function init() { + const toggle = document.querySelector(SELECTORS.toggle); + const submenuToggles = document.querySelectorAll(SELECTORS.submenuToggle); + + // Set initial header height + updateHeaderHeight(); + + if (toggle) { + toggle.addEventListener('click', handleMenuToggle); + } + + submenuToggles.forEach(btn => { + btn.addEventListener('click', handleSubmenuToggle); + }); + + // Close menu on resize to desktop + window.addEventListener('resize', handleResize); + + // Close menu on Escape key + document.addEventListener('keydown', handleKeydown); + } + + function handleMenuToggle(e) { + const toggle = e.currentTarget; + menuOpen = !menuOpen; + + toggle.classList.toggle(CLASSES.active, menuOpen); + toggle.setAttribute('aria-expanded', String(menuOpen)); + document.body.classList.toggle(CLASSES.menuOpen, menuOpen); + } + + function handleSubmenuToggle(e) { + e.preventDefault(); + e.stopPropagation(); + + const btn = e.currentTarget; + const parent = btn.closest(SELECTORS.hasChildren); + + if (!parent) return; + + const isOpen = parent.classList.contains(CLASSES.submenuOpen); + + // Close siblings at the same level + const siblings = parent.parentElement.querySelectorAll(':scope > ' + SELECTORS.hasChildren); + siblings.forEach(sibling => { + if (sibling !== parent) { + sibling.classList.remove(CLASSES.submenuOpen); + const sibBtn = sibling.querySelector(':scope > ' + SELECTORS.submenuToggle); + if (sibBtn) sibBtn.setAttribute('aria-expanded', 'false'); + } + }); + + // Toggle current + parent.classList.toggle(CLASSES.submenuOpen, !isOpen); + btn.setAttribute('aria-expanded', String(!isOpen)); + } + + function handleResize() { + // Update header height on resize + updateHeaderHeight(); + + const breakpoint = getBreakpointDesktop(); + if (window.innerWidth >= breakpoint && menuOpen) { + closeMenu(); + } + } + + function handleKeydown(e) { + if (e.key === 'Escape' && menuOpen) { + closeMenu(); + // Return focus to toggle button + const toggle = document.querySelector(SELECTORS.toggle); + if (toggle) toggle.focus(); + } + } + + function closeMenu() { + const toggle = document.querySelector(SELECTORS.toggle); + menuOpen = false; + + if (toggle) { + toggle.classList.remove(CLASSES.active); + toggle.setAttribute('aria-expanded', 'false'); + } + + document.body.classList.remove(CLASSES.menuOpen); + + // Close all submenus + document.querySelectorAll('.' + CLASSES.submenuOpen).forEach(el => { + el.classList.remove(CLASSES.submenuOpen); + const btn = el.querySelector(':scope > ' + SELECTORS.submenuToggle); + if (btn) btn.setAttribute('aria-expanded', 'false'); + }); + } + + // Initialize on DOM ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/website/themes/sambee/layouts/_default/baseof.html b/website/themes/sambee/layouts/_default/baseof.html index 2d0d268..1f1ee24 100644 --- a/website/themes/sambee/layouts/_default/baseof.html +++ b/website/themes/sambee/layouts/_default/baseof.html @@ -1,101 +1,17 @@ +{{- $initialTheme := cond (eq site.Params.theme_default "dark") "dark" "" -}} - + - - - {{ if .Title }}{{ .Title }} | {{ site.Title }}{{ else }}{{ site.Title }}{{ end }} - - + {{ partial "essentials/head.html" . }} + {{ partialCached "essentials/style.html" . }} -
- - -
-
+ {{ partial "essentials/header.html" . }} + {{ partial "search-modal.html" . }} +
{{ block "main" . }}{{ end }}
-
- Sambee website scaffold for Hugo migration validation. -
+ {{ partial "essentials/footer.html" . }} + {{ partialCached "essentials/script.html" . }} diff --git a/website/themes/sambee/layouts/_default/list.html b/website/themes/sambee/layouts/_default/list.html index 0e9fa66..1ca5508 100644 --- a/website/themes/sambee/layouts/_default/list.html +++ b/website/themes/sambee/layouts/_default/list.html @@ -1,8 +1,10 @@ {{ define "main" }} -
-

{{ .Title }}

-
- {{ .Content }} -
-
+
+
+

{{ .Title }}

+
+ {{ .Content }} +
+
+
{{ end }} diff --git a/website/themes/sambee/layouts/_default/single.html b/website/themes/sambee/layouts/_default/single.html index 0e9fa66..1ca5508 100644 --- a/website/themes/sambee/layouts/_default/single.html +++ b/website/themes/sambee/layouts/_default/single.html @@ -1,8 +1,10 @@ {{ define "main" }} -
-

{{ .Title }}

-
- {{ .Content }} -
-
+
+
+

{{ .Title }}

+
+ {{ .Content }} +
+
+
{{ end }} diff --git a/website/themes/sambee/layouts/docs/list.html b/website/themes/sambee/layouts/docs/list.html index 1906bfd..754a39e 100644 --- a/website/themes/sambee/layouts/docs/list.html +++ b/website/themes/sambee/layouts/docs/list.html @@ -3,51 +3,65 @@ {{ $navData := index hugo.Data.docs_nav $currentVersion }} {{ if and (not .Params.product_version) (not .Params.book) }} -
-

{{ .Title }}

-
{{ .Content }}
+
+
+ Documentation +

{{ .Title }}

+
{{ .Content }}
+
{{ with $navData }}
{{ range .books }} -
+
+ Version {{ $currentVersion }}

{{ .title }}

-

Current version: {{ $currentVersion }}

-
+

{{ len .sections }} section{{ if ne (len .sections) 1 }}s{{ end }} available in the current documentation set.

+
{{ end }} {{ end }} - + {{ else if and .Params.product_version (not .Params.book) }} -
-

{{ .Title }}

-
{{ .Content }}
+
+
+ Version {{ .Params.product_version }} +

{{ .Title }}

+
{{ .Content }}
+
{{ $versionNav := index hugo.Data.docs_nav .Params.product_version }} {{ with $versionNav }}
{{ range .books }} -
+
+ Book

{{ .title }}

-

Book for Sambee {{ $.Params.product_version }}

-
+

Canonical documentation for Sambee {{ $.Params.product_version }}.

+
{{ end }} {{ end }} - + {{ else if and .Params.product_version .Params.book }} -
-
+ {{ else }} -
-

{{ .Title }}

-
{{ .Content }}
-
+
+
+

{{ .Title }}

+
{{ .Content }}
+
+
{{ end }} {{ end }} diff --git a/website/themes/sambee/layouts/docs/single.html b/website/themes/sambee/layouts/docs/single.html index d874a08..793dae9 100644 --- a/website/themes/sambee/layouts/docs/single.html +++ b/website/themes/sambee/layouts/docs/single.html @@ -1,12 +1,16 @@ {{ define "main" }} -
-
+ {{ end }} diff --git a/website/themes/sambee/layouts/home.html b/website/themes/sambee/layouts/home.html index 0e9fa66..2aa1612 100644 --- a/website/themes/sambee/layouts/home.html +++ b/website/themes/sambee/layouts/home.html @@ -1,8 +1,54 @@ {{ define "main" }} - + + + +{{ with $navData }} +
+
+ Documentation Books +

Start in the book that matches your task

+
+
+ {{ range .books }} +
+ {{ $currentVersion }} +

{{ .title }}

+

{{ len .sections }} section{{ if ne (len .sections) 1 }}s{{ end }} in the first release.

+
+ {{ end }} +
+
+{{ end }} {{ end }} diff --git a/website/themes/sambee/layouts/partials/docs/sidebar.html b/website/themes/sambee/layouts/partials/docs/sidebar.html index 6951ddb..1566d5c 100644 --- a/website/themes/sambee/layouts/partials/docs/sidebar.html +++ b/website/themes/sambee/layouts/partials/docs/sidebar.html @@ -7,18 +7,18 @@ {{ if and $navData $book }} {{ range $navData.books }} {{ if eq .slug $book }} - {{ range .sections }} -