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 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/backend/requirements-dev.lock.txt b/backend/requirements-dev.lock.txt index 4e0427d..a3ccdf2 100644 --- a/backend/requirements-dev.lock.txt +++ b/backend/requirements-dev.lock.txt @@ -297,56 +297,56 @@ coverage[toml]==7.13.5 \ --hash=sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0 \ --hash=sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f # via pytest-cov -cryptography==46.0.6 \ - --hash=sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70 \ - --hash=sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d \ - --hash=sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a \ - --hash=sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0 \ - --hash=sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97 \ - --hash=sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30 \ - --hash=sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759 \ - --hash=sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c \ - --hash=sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead \ - --hash=sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275 \ - --hash=sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58 \ - --hash=sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f \ - --hash=sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361 \ - --hash=sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507 \ - --hash=sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa \ - --hash=sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b \ - --hash=sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b \ - --hash=sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8 \ - --hash=sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8 \ - --hash=sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72 \ - --hash=sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175 \ - --hash=sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e \ - --hash=sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124 \ - --hash=sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a \ - --hash=sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c \ - --hash=sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f \ - --hash=sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d \ - --hash=sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4 \ - --hash=sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c \ - --hash=sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290 \ - --hash=sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca \ - --hash=sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d \ - --hash=sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a \ - --hash=sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed \ - --hash=sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a \ - --hash=sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb \ - --hash=sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8 \ - --hash=sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707 \ - --hash=sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410 \ - --hash=sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736 \ - --hash=sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2 \ - --hash=sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4 \ - --hash=sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013 \ - --hash=sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19 \ - --hash=sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b \ - --hash=sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738 \ - --hash=sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463 \ - --hash=sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77 \ - --hash=sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4 +cryptography==46.0.7 \ + --hash=sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65 \ + --hash=sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832 \ + --hash=sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067 \ + --hash=sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de \ + --hash=sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4 \ + --hash=sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0 \ + --hash=sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b \ + --hash=sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968 \ + --hash=sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef \ + --hash=sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b \ + --hash=sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4 \ + --hash=sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3 \ + --hash=sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308 \ + --hash=sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e \ + --hash=sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163 \ + --hash=sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f \ + --hash=sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee \ + --hash=sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77 \ + --hash=sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85 \ + --hash=sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99 \ + --hash=sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7 \ + --hash=sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83 \ + --hash=sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85 \ + --hash=sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006 \ + --hash=sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb \ + --hash=sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e \ + --hash=sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba \ + --hash=sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325 \ + --hash=sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d \ + --hash=sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1 \ + --hash=sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1 \ + --hash=sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2 \ + --hash=sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0 \ + --hash=sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455 \ + --hash=sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842 \ + --hash=sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457 \ + --hash=sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15 \ + --hash=sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2 \ + --hash=sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c \ + --hash=sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb \ + --hash=sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5 \ + --hash=sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4 \ + --hash=sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902 \ + --hash=sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246 \ + --hash=sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022 \ + --hash=sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f \ + --hash=sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e \ + --hash=sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298 \ + --hash=sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce # via # -r requirements.txt # pyspnego diff --git a/backend/requirements.lock.txt b/backend/requirements.lock.txt index 468e37b..1e30809 100644 --- a/backend/requirements.lock.txt +++ b/backend/requirements.lock.txt @@ -147,56 +147,56 @@ click==8.3.1 \ --hash=sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a \ --hash=sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6 # via uvicorn -cryptography==46.0.6 \ - --hash=sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70 \ - --hash=sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d \ - --hash=sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a \ - --hash=sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0 \ - --hash=sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97 \ - --hash=sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30 \ - --hash=sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759 \ - --hash=sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c \ - --hash=sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead \ - --hash=sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275 \ - --hash=sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58 \ - --hash=sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f \ - --hash=sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361 \ - --hash=sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507 \ - --hash=sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa \ - --hash=sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b \ - --hash=sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b \ - --hash=sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8 \ - --hash=sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8 \ - --hash=sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72 \ - --hash=sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175 \ - --hash=sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e \ - --hash=sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124 \ - --hash=sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a \ - --hash=sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c \ - --hash=sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f \ - --hash=sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d \ - --hash=sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4 \ - --hash=sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c \ - --hash=sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290 \ - --hash=sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca \ - --hash=sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d \ - --hash=sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a \ - --hash=sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed \ - --hash=sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a \ - --hash=sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb \ - --hash=sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8 \ - --hash=sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707 \ - --hash=sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410 \ - --hash=sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736 \ - --hash=sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2 \ - --hash=sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4 \ - --hash=sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013 \ - --hash=sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19 \ - --hash=sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b \ - --hash=sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738 \ - --hash=sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463 \ - --hash=sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77 \ - --hash=sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4 +cryptography==46.0.7 \ + --hash=sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65 \ + --hash=sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832 \ + --hash=sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067 \ + --hash=sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de \ + --hash=sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4 \ + --hash=sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0 \ + --hash=sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b \ + --hash=sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968 \ + --hash=sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef \ + --hash=sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b \ + --hash=sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4 \ + --hash=sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3 \ + --hash=sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308 \ + --hash=sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e \ + --hash=sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163 \ + --hash=sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f \ + --hash=sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee \ + --hash=sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77 \ + --hash=sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85 \ + --hash=sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99 \ + --hash=sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7 \ + --hash=sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83 \ + --hash=sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85 \ + --hash=sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006 \ + --hash=sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb \ + --hash=sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e \ + --hash=sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba \ + --hash=sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325 \ + --hash=sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d \ + --hash=sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1 \ + --hash=sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1 \ + --hash=sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2 \ + --hash=sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0 \ + --hash=sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455 \ + --hash=sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842 \ + --hash=sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457 \ + --hash=sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15 \ + --hash=sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2 \ + --hash=sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c \ + --hash=sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb \ + --hash=sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5 \ + --hash=sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4 \ + --hash=sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902 \ + --hash=sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246 \ + --hash=sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022 \ + --hash=sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f \ + --hash=sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e \ + --hash=sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298 \ + --hash=sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce # via # -r requirements.txt # pyspnego diff --git a/backend/requirements.txt b/backend/requirements.txt index f17b8ce..a33fadc 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,7 +2,7 @@ fastapi==0.120.4 uvicorn[standard]==0.38.0 python-multipart==0.0.22 sqlmodel==0.0.27 -cryptography==46.0.6 +cryptography==46.0.7 python-jose[cryptography]==3.5.0 argon2-cffi==25.1.0 smbprotocol==1.15.0 diff --git a/documentation_planning/website/AI_visual_design_plan.md b/documentation_planning/website/AI_visual_design_plan.md new file mode 100644 index 0000000..bfe7bd9 --- /dev/null +++ b/documentation_planning/website/AI_visual_design_plan.md @@ -0,0 +1,489 @@ +# AI Visual Design Plan For Sambee Website + +## Purpose + +Define a practical AI-assisted workflow for the next major phase of the Sambee website: visual design. + +This plan assumes: + +- the Hugo site and custom theme foundation already exist +- the site structure and documentation model are already planned +- the next problem is visual direction, brand translation, and component styling + +## Goal + +Use AI to accelerate visual design exploration without letting AI make core brand or product decisions. + +The output of this phase should be: + +- one approved visual direction for the website +- a website-specific design token system aligned with Sambee branding +- approved desktop and mobile mockups for the homepage and docs shell +- a component styling plan that can be implemented in the Hugo theme + +## Non-Goals + +This phase is not about: + +- rewriting site information architecture +- migrating content in depth +- generating production-ready code directly from AI tools +- accepting generic AI-generated SaaS aesthetics without review + +## Current Context + +The current website foundation is structurally usable, but the visual system is still transitional. + +Relevant facts: + +- the website already has a working Sambee-specific Hugo theme and layout shell +- the homepage and docs pages already exist as placeholders +- the docs structure is versioned and routed correctly +- the current website token file still reflects donor-era palette and typography choices more than the product brand +- the product itself already has stronger branding cues than the website does today + +This means AI should be used first for visual direction and token design, not for code generation. + +## Recommended AI Role + +AI should be used as: + +- a fast concept generator +- a visual exploration partner +- a style-system drafting assistant +- a design critique assistant + +AI should not be treated as: + +- the final art director +- the source of brand truth +- a reliable source of implementation-ready front-end code + +## Recommended Tool Roles + +### Figma AI + +Best for: + +- editable visual concepts +- layout and component exploration +- design iteration with real design files +- critique and refinement inside a collaborative design workflow + +Use when: + +- exploring page compositions +- refining selected directions +- preparing handoff-quality mockups + +### Relume + +Best for: + +- rapid sitemap-to-wireframe generation +- quick style-guide experimentation +- generating alternate marketing-site structures and section layouts + +Use when: + +- comparing multiple homepage structures quickly +- testing different section patterns before detailed design work + +### Uizard + +Best for: + +- rough prompt-to-mockup generation +- screenshot-to-editable-draft workflows +- early broad exploration when speed matters more than fidelity + +Use when: + +- generating multiple low-commitment page directions quickly + +### v0 + +Best for: + +- coded visual experiments +- component-level exploration after a design direction already exists +- pressure-testing layout ideas in responsive HTML/CSS + +Use when: + +- the visual direction is already chosen +- the goal is to evaluate implementation patterns, not invent the brand + +### Adobe Firefly Or Canva + +Best for: + +- moodboards +- art direction support +- background or supporting imagery exploration + +Use when: + +- the site needs supporting visuals or stylistic reference material +- the team wants fast image-based exploration without touching layout structure + +## Sambee Design Constraints + +Any AI-generated direction must respect these constraints: + +- Sambee is a practical technical product, not a lifestyle brand +- the site is docs-first, not marketing-only +- documentation readability matters as much as homepage appeal +- the docs are effectively text-only and should not depend on illustration-heavy or image-heavy layouts +- the design must work for both landing pages and dense docs pages +- the design should feel trustworthy, precise, efficient, and desktop-adjacent +- the design should express visual elegance through typography, spacing, rhythm, proportion, and restraint rather than decorative excess +- avoid generic purple-gradient SaaS styling +- avoid overly playful illustration-heavy startup aesthetics +- avoid visual systems that depend on one-off hero tricks and collapse in docs pages + +## Deliverables + +The visual design phase should produce these concrete outputs. + +### Deliverable 1: Three Distinct Art Directions + +Each direction should include: + +- palette +- typography pairing +- component character +- page mood +- imagery direction +- rationale + +The directions should be meaningfully different from each other. + +### Deliverable 2: Selected Design Token System + +The chosen direction should be converted into: + +- semantic color tokens +- typography tokens +- spacing scale +- border radius rules +- elevation and border treatment rules +- component state styling rules +- light and dark theme guidance if both will be supported on the website + +### Deliverable 3: Key Page Mockups + +At minimum: + +- homepage desktop +- homepage mobile +- docs landing page desktop +- docs book page desktop +- docs book page mobile + +### Deliverable 4: Component Styling Spec + +At minimum: + +- header and navigation +- hero section +- buttons and links +- cards +- docs sidebar +- version switcher +- metadata chips +- callouts and banners +- code block framing +- footer + +## Content Anchors For AI Prompts + +AI-generated visual work will be more relevant if it is grounded in real Sambee messaging instead of abstract product descriptions. + +Do not paste the entire homepage copy document into every prompt. + +Use selected content anchors from `Homepage_text_copy.md` so the tool has: + +- a real headline and subheadline +- real benefit language +- real CTA labels +- real product constraints and value framing + +Recommended content to include in generation prompts: + +- hero headline: + - `Browser-based file access for SMB shares and local drives` +- hero subheadline: + - `Sambee provides browser-based access to SMB shares and local drives. Explore, preview, and manage files directly in the browser, with the companion app extending Sambee to the local desktop when needed. Sambee enables browser-first file access without requiring files to be moved into the cloud.` +- supporting points: + - `Self-hosted` + - `Desktop and mobile` + - `Companion optional` +- CTA labels: + - `See Features` + - `Admin Docs` + - `Read Docs` +- core value statement: + - `Sambee gives teams browser-based file access without forcing them into a cloud-first storage model.` +- deployment framing: + - `Deploy Sambee where your files already live and keep access under your control.` + +If the tool supports longer context well, also include selected benefit themes: + +- self-hosted control +- better everyday file handling +- rich previews before download +- native editing when needed +- built for desktop and mobile +- fits existing infrastructure + +These content anchors help the AI choose layouts, emphasis, and hierarchy that fit the real product instead of a generic SaaS landing page. + +## Execution Plan + +### Step 1: Write The Brief + +Create one strong brief before touching any AI tool. + +Why: + +- weak prompts produce generic work +- the same brief should be reused across tools for comparable output + +Output: + +- one approved design brief +- one approved set of content anchors taken from homepage copy + +### Step 2: Generate Multiple Directions + +Use the brief to generate 3 to 5 clearly different visual directions. + +Rules: + +- do not ask for one direction only +- require explicit contrast between directions +- ask each tool for rationale, not just visuals + +Output: + +- a shortlist of directions worth review + +### Step 3: Human Review And Selection + +Review directions against product fit. + +Review criteria: + +- trustworthiness +- clarity +- docs readability +- brand distinctiveness +- implementation realism + +Output: + +- one primary direction +- optionally one backup direction + +### Step 4: Convert Direction Into Tokens + +Use AI to help translate the selected direction into a design system. + +Output: + +- a token proposal that can replace the current transitional website palette and typography + +### Step 5: Design The Critical Surfaces + +Apply the selected system to: + +- homepage +- docs landing page +- docs article page + +Output: + +- reviewable page mockups with desktop and mobile coverage + +### Step 6: Critique And Stress-Test + +Use AI and human review to critique the mockups. + +Test for: + +- accessibility and contrast risk +- mobile density problems +- weak hierarchy +- over-designed hero sections +- docs fatigue over long reading sessions +- inconsistency between marketing and docs pages + +Output: + +- revision list + +### Step 7: Prepare For Implementation + +Turn the approved design into implementation-ready guidance. + +Output: + +- design tokens +- component rules +- page priorities +- notes for Hugo theme implementation + +## Decision Framework + +When comparing AI-generated directions, use this scorecard. + +Rate each direction from 1 to 5 on: + +- brand fit +- trust and professionalism +- docs readability +- visual distinctiveness +- implementation realism +- mobile adaptability +- reuse across homepage and docs + +Directions that score well visually but poorly on docs readability should be rejected. + +## Risks + +### Risk: Generic SaaS Output + +AI often defaults to polished but interchangeable startup visuals. + +Mitigation: + +- explicitly ban generic SaaS patterns in the brief +- require multiple differentiated directions +- keep a human review gate before any implementation work + +### Risk: Marketing-Landing Bias + +AI tools often optimize for splashy landing pages, not documentation-heavy surfaces. + +Mitigation: + +- require docs page mockups early +- evaluate design directions on article-page readability, not just hero design + +### Risk: Brand Drift From The Product + +The website could drift away from the actual Sambee application identity. + +Mitigation: + +- anchor the brief to Sambee product values and existing product branding +- treat website tokens as an extension of the product identity, not a separate brand + +### Risk: Low-Fidelity Code Generation Too Early + +If code generation starts before the visual direction is chosen, the project will converge on generic implementation patterns. + +Mitigation: + +- do not use AI code generation as the first design step +- use coded experiments only after direction and tokens are chosen + +## Concrete AI Design Brief + +Use this brief as the baseline prompt for Figma AI, Relume, Uizard, v0, or similar tools. + +### Brief + +Design a visual direction for the Sambee website. + +Sambee is a browser-based product for accessing SMB shares and local drives. The website is a docs-first product site, not a pure marketing landing page. It must support both a product homepage and dense versioned documentation. The product logo will be uploaded together with this prompt and should be used as a brand anchor. + +Use these real homepage content anchors when deciding hierarchy and emphasis: + +- headline: `Browser-based file access for SMB shares and local drives` +- subheadline: `Sambee provides browser-based access to SMB shares and local drives. Explore, preview, and manage files directly in the browser, with the companion app extending Sambee to the local desktop when needed. Sambee enables browser-first file access without requiring files to be moved into the cloud.` +- supporting points: `Self-hosted`, `Desktop and mobile`, `Companion optional` +- CTA labels: `See Features`, `Admin Docs`, `Read Docs` +- core value statement: `Sambee gives teams browser-based file access without forcing them into a cloud-first storage model.` +- deployment framing: `Deploy Sambee where your files already live and keep access under your control.` + +Treat these content anchors as real copy constraints, not placeholder text. + +The visual tone should feel: + +- trustworthy +- precise +- efficient +- technical without feeling cold +- desktop-adjacent and productivity-oriented +- distinctive, but not flashy +- typographically strong +- visually elegant through restraint, proportion, and polish + +The design must avoid: + +- generic startup SaaS aesthetics +- purple gradients +- playful cartoon illustrations +- crypto or AI-brand visual clichés +- visual systems that look good only in hero sections and fail on docs pages + +The website should feel more like a serious tool for real work than a hype-driven product launch. Favor strong typography, careful spacing, confident composition, and elegant visual hierarchy over heavy decoration. + +Base the visual language on these brand cues: + +- warm golden yellow accents +- charcoal or deep neutral structure +- cream or warm light surfaces where appropriate +- strong typography and restrained color use +- clean technical layouts with a sense of craft +- editorial clarity and refined page rhythm + +Create 3 distinct visual directions for the website. For each direction, include: + +- a color palette with semantic roles +- typography recommendations +- homepage art direction +- docs page art direction for documentation that is effectively text-only +- component styling notes for cards, navigation, sidebar, buttons, chips, and callouts +- imagery or illustration direction if used +- a short rationale explaining why the direction fits Sambee + +Make the homepage concepts feel plausible for the supplied copy, especially the long subheadline and the text-first documentation emphasis. + +All directions must work across: + +- homepage desktop and mobile +- documentation landing page +- documentation article page with sidebar and version switcher + +Optimize for: + +- readability +- strong hierarchy +- typographic quality +- visual elegance without ornament for its own sake +- calm but confident branding +- responsive behavior +- consistency between marketing and docs surfaces +- realistic implementation in a Hugo-based website with reusable theme tokens + +### Short Prompt Variant + +Create 3 distinct visual directions for a docs-first product website for Sambee, a browser-based SMB and local-drive access tool. The product logo will be uploaded with this prompt and should be used as a brand anchor. Use these content anchors as real homepage copy constraints: headline `Browser-based file access for SMB shares and local drives`; supporting points `Self-hosted`, `Desktop and mobile`, `Companion optional`; CTA labels `See Features`, `Admin Docs`, `Read Docs`; core message `browser-based file access without forcing a cloud-first storage model`. The site must feel trustworthy, technical, efficient, desktop-adjacent, typographically strong, and visually elegant through restraint. Use warm golden accents, charcoal structure, cream or warm light surfaces, and refined typography-led design. The docs are effectively text-only, so the system must rely on hierarchy, spacing, rhythm, and component discipline rather than imagery. Avoid generic SaaS visuals, purple gradients, cartoon illustration, and over-designed hero sections. Each direction must include homepage and docs-page styling, semantic palette, type choices, component character, and rationale. + +### Critique Prompt Variant + +Review this Sambee website concept as a senior product designer. Evaluate brand fit, trustworthiness, docs readability, visual hierarchy, mobile behavior, accessibility risk, and consistency between homepage and docs pages. Identify weak points and propose concrete revisions. + +## Recommended Next Action + +Run the concrete brief through one design-file-first tool and one structure-first tool. + +Recommended pair: + +- Figma AI for editable visual exploration +- Relume for alternate homepage and section structures + +After that, compare outputs and choose one direction before writing any implementation CSS. diff --git a/documentation_planning/website/Hugo_migration_plan.md b/documentation_planning/website/Hugo_migration_plan.md new file mode 100644 index 0000000..0e5d227 --- /dev/null +++ b/documentation_planning/website/Hugo_migration_plan.md @@ -0,0 +1,579 @@ +# Hugo Migration Plan For Sambee Website + +## Goal + +Build a new Hugo site for Sambee inside this repository, using `website-temp` only as a donor for reusable theme and build pieces. + +The migration should produce a clean `website/` directory with: + +- 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 + +## Fixed Decisions + +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: 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` + +## Current Implementation Status + +Status legend used below: + +- `implemented`: present in `website/` today +- `partial`: started and usable, but not fully aligned with the target plan yet +- `not started`: still planned work + +Current snapshot as of 2026-04-09: + +- `implemented`: clean `website/` Hugo site scaffold exists +- `implemented`: active theme is `sambee` +- `implemented`: initial versioned docs tree exists under `content/docs/1.0/` +- `implemented`: `data/docs-versions.toml` and `data/docs-nav/1.0.toml` exist +- `implemented`: docs sidebar, version switcher, and unversioned book redirects exist +- `implemented`: build scripts include Pagefind +- `implemented`: comments and PWA wiring are absent from the active site +- `partial`: donor theme extraction is in place, but final cleanup is not complete yet +- `partial`: docs behavior is present, but breadcrumbs, version-status banners, and stronger search/version integrity behavior are still missing +- `partial`: placeholder homepage and docs content exist, but the migration is not content-complete or hardened + +## 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 + +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. + +## Target Architecture + +### Directory Layout + +The new site should live in `website/` with this structure: + +```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 +|-- data/ +| `-- docs-versions.toml +|-- layouts/ +|-- static/ +|-- themes/ +| `-- sambee/ +| |-- assets/ +| |-- i18n/ +| `-- layouts/ +|-- scripts/ +|-- hugo.toml +|-- go.mod +|-- go.sum +|-- package.json +`-- package-lock.json +``` + +### Routing Model + +Canonical docs URLs must be versioned. + +Examples: + +- `/docs/1.0/end-user/` +- `/docs/1.0/end-user/install/` +- `/docs/1.0/admin/configuration/` +- `/docs/1.0/developer/architecture/` + +Unversioned docs book routes are convenience entry points, not canonical content locations. + +Examples: + +- `/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/` + +There must not be duplicate full content trees at both versioned and unversioned URLs. + +### Hugo Content Rules + +- 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. + +### Version Metadata + +`data/docs-versions.toml` should be the single source of truth for visible and supported versions. + +It should define at least: + +- `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 + +Suggested status policy: + +- `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 + +Recommended shape: + +```toml +current = "1.0" + +[[versions]] +slug = "1.0" +label = "1.0" +status = "current" +searchable = true +``` + +### Navigation And Ordering Strategy + +Use an explicit ordered navigation definition as the source of truth for docs ordering. + +Why this is the best fit: + +- 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. + +Recommended rule set: + +- 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 + +Recommended navigation data layout: + +```text +website/ +|-- data/ +| |-- docs-versions.toml +| `-- docs-nav/ +| `-- 1.0.toml +``` + +Recommended `data/docs-nav/1.0.toml` shape: + +```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"] + +[[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. + +### Docs Page Front Matter Contract + +Every versioned docs page should include: + +- `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` + +Optional fields: + +- `layout` +- `description` +- `version_aliases` +- `legacy_paths` + +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. + +Example: + +```toml ++++ +title = "Install Sambee" +doc_id = "install" +product_version = "1.0" +book = "end-user" ++++ +``` + +### Version Switcher Rules + +- 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. + +### Search Rules + +Pagefind is mandatory. + +Search must: + +- 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` + +## Migration Scope + +### Copy Early + +- `implemented`: `website-temp/themes/helgeklein/assets/**` -> `website/themes/sambee/assets/**` +- `implemented`: `website-temp/themes/helgeklein/i18n/**` -> `website/themes/sambee/i18n/**` +- `implemented`: `website-temp/themes/helgeklein/layouts/**` -> `website/themes/sambee/layouts/**` +- `implemented`: `website-temp/data/theme.json` -> `website/data/theme.json` +- `implemented`: `website-temp/scripts/themeGenerator.js` -> `website/scripts/themeGenerator.js` +- `implemented`: Pagefind-related UI assets are wired into the current site build + +### Recreate Manually + +- `implemented`: `website/hugo.toml` +- `implemented`: `website/config/_default/params.toml` +- `implemented`: `website/config/_default/menus.en.toml` +- `implemented`: `website/config/_default/module.toml` +- `implemented`: `website/data/docs-versions.toml` +- `implemented`: `website/data/docs-nav/1.0.toml` +- `partial`: `website/content/**` exists for the homepage and initial `1.0` docs tree, but content migration is still incomplete + +### Defer Until Needed + +- `not started`: `.htmltest.yml` +- `not started`: `tests/test_menu_urls.py` +- `not started`: optional Hugo modules such as `videos`, `site-verifications`, and `button` + +### Do Not Carry Over + +- 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 + +## Implementation Plan + +### Phase 1: Scaffold A Clean Site + +Status: `implemented` + +Create `website/` and add: + +- `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` + +Exit condition: + +- Hugo builds a placeholder Sambee site without using `website-temp` content. + +### Phase 2: Extract And Clean The Theme + +Status: `partial` + +Create `themes/sambee/` and copy the donor theme source. + +Keep: + +- base templates +- essentials partials +- icons +- image render hooks +- generic article and docs shell pieces + +Remove or replace immediately: + +- blog home template +- blog section templates +- taxonomy and term templates +- comments partials +- helgeklein branding + +Exit condition: + +- The theme renders a generic Sambee page shell with no blog dependency. + +What is already true: + +- `themes/sambee/` exists and is the active theme +- Sambee-specific home and docs layouts are in place +- comments and PWA wiring are absent from the active site + +What still appears to remain: + +- final donor cleanup is incomplete +- some non-doc routes from generic Hugo defaults still build, so the site is not fully reduced to the intended product/docs surface yet + +### Phase 3: Rebuild The Asset And Search Pipeline + +Status: `implemented` + +Set up only the dependencies still needed for Sambee: + +- Tailwind CSS +- theme token generation +- Pagefind +- optional formatter tooling if wanted for the `website/` subproject + +Required scripts: + +- `dev` +- `build` +- `preview` +- `build:search` + +Build requirements: + +- generate `generated-theme.css` +- build the Hugo site +- run Pagefind against `website/public` + +Exit condition: + +- local dev and production builds both generate a working search index. + +### Phase 4: Implement Versioned Docs Behavior + +Status: `partial` + +Implement docs-specific templates and data-driven behavior for: + +- version switcher +- docs side navigation +- breadcrumbs +- version status banners +- hard redirects for unversioned docs book entry points + +Implementation rules: + +- 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` + +Already implemented: + +- version switcher +- docs side navigation driven by `data/docs-nav/1.0.toml` +- `doc_id` based page matching across versions +- hard redirects for unversioned docs book entry points +- shared docs templates for book landing pages and docs pages + +Still missing from this phase: + +- breadcrumbs +- version status banners +- broader multi-version behavior beyond the initial `1.0` set +- explicit confirmation that search defaults to the current docs version context + +Exit condition: + +- users can move between supported versions predictably and understand which version they are viewing. + +### Phase 5: Migrate Content And Harden The Site + +Status: `partial` + +Migrate homepage and documentation material from the planning docs and existing internal docs. + +Then add: + +- validation checks +- link checking +- version integrity checks +- deployment automation for `sambee.net` + +Required automated checks: + +- 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 + +Exit condition: + +- the site is content-complete enough to replace the donor implementation path. + +Current note: + +- placeholder homepage and placeholder docs pages exist +- hardening and validation work have not started yet +- deployment automation for `sambee.net` is not implemented yet + +## Main Risks + +### Hidden Blog Coupling In Donor Templates + +Risk: + +Generic-looking partials may still assume blog fields, featured images, post lists, or taxonomy terms. + +Mitigation: + +- remove blog templates first +- smoke-test copied partials against minimal docs pages +- prefer explicit Sambee overrides over preserving donor behavior by default + +### Version Drift Across Releases + +Risk: + +Equivalent docs pages may stop matching across releases, breaking the switcher. + +Mitigation: + +- require `doc_id` +- keep folder structures aligned where possible +- validate cross-version mapping in CI + +### Ordering Drift Between Content And Reader Navigation + +Risk: + +Folder order, titles, and other inferred ordering signals can drift away from the intended reading order, making navigation inconsistent. + +Mitigation: + +- 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 + +### Search Noise Across Multiple Versions + +Risk: + +Users may get results from the wrong version. + +Mitigation: + +- 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: + +The new site becomes hard to maintain if generic rendering logic stays in the site root. + +Mitigation: + +- keep reusable presentation in `themes/sambee` +- keep content, configuration, and Sambee-specific overrides in the site root + +## Definition Of Done + +The migration is structurally complete when all of the following are true: + +1. `implemented`: `website-temp` is no longer part of the active implementation path. +2. `implemented`: `website/` builds without donor content. +3. `implemented`: the active theme is `sambee`. +4. `partial`: Helge Klein branding is no longer visible in the active site, but donor cleanup should still be treated as incomplete until the remaining generic/blog residue is removed. +5. `implemented`: canonical docs URLs are versioned and use plain version slugs such as `1.0`. +6. `implemented`: docs content lives under `docs//end-user`, `docs//admin`, and `docs//developer`. +7. `partial`: Pagefind is wired into the build, but version-aware search behavior should still be verified against the final multi-version implementation. +8. `implemented`: comments and PWA functionality are absent. +9. `implemented` for the current scaffold: version switching, docs book entry points, and docs navigation resolve predictably for `1.0`. +10. `implemented`: docs sidebar order is determined by explicit navigation data, not inferred ordering. + +## Immediate Next Step + +Finish the first incomplete layer rather than re-scaffolding the site: + +- complete donor/theme cleanup so only the intended Sambee surface remains +- add the missing docs UX pieces: breadcrumbs and version-status banners +- verify and tighten search behavior for version scoping +- migrate real homepage and documentation content +- add validation and integrity checks before deployment work begins + +The minimum viable scaffold already exists. The next useful work is to turn that scaffold into a complete and hardened Sambee site. 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. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8add791..2fe0622 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,7 +16,7 @@ "@react-spring/web": "^10.0.3", "@tanstack/react-virtual": "^3.13.22", "@use-gesture/react": "^10.3.1", - "axios": "1.13.6", + "axios": "1.15.0", "i18next": "^25.1.3", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -4641,14 +4641,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.0.tgz", - "integrity": "sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.4", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -4656,14 +4656,14 @@ "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.0", - "vitest": "4.1.0" + "@vitest/browser": "4.1.4", + "vitest": "4.1.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -4672,31 +4672,31 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", - "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", "chai": "^6.2.2", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", - "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.0", + "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -4705,7 +4705,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -4717,26 +4717,26 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", - "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", - "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.4", "pathe": "^2.0.3" }, "funding": { @@ -4744,14 +4744,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", - "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -4760,9 +4760,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", - "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", "dev": true, "license": "MIT", "funding": { @@ -4770,37 +4770,37 @@ } }, "node_modules/@vitest/ui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.0.tgz", - "integrity": "sha512-sTSDtVM1GOevRGsCNhp1mBUHKo9Qlc55+HCreFT4fe99AHxl1QQNXSL3uj4Pkjh5yEuWZIx8E2tVC94nnBZECQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.4.tgz", + "integrity": "sha512-EgFR7nlj5iTDYZYCvavjFokNYwr3c3ry0sFiCg+N7B233Nwp+NNx7eoF/XvMWDCKY71xXAG3kFkt97ZHBJVL8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.4", "fflate": "^0.8.2", - "flatted": "3.4.0", + "flatted": "^3.4.2", "pathe": "^2.0.3", "sirv": "^3.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.0" + "vitest": "4.1.4" } }, "node_modules/@vitest/utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", - "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.0", + "@vitest/pretty-format": "4.1.4", "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -4938,14 +4938,14 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", - "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "proxy-from-env": "^2.1.0" } }, "node_modules/babel-plugin-macros": { @@ -5369,9 +5369,9 @@ } }, "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -5949,9 +5949,9 @@ "license": "MIT" }, "node_modules/flatted": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.0.tgz", - "integrity": "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -8278,9 +8278,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "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": { @@ -8380,10 +8380,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/punycode": { "version": "2.3.1", @@ -9619,9 +9622,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9694,19 +9697,19 @@ } }, "node_modules/vitest": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", - "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.0", - "@vitest/mocker": "4.1.0", - "@vitest/pretty-format": "4.1.0", - "@vitest/runner": "4.1.0", - "@vitest/snapshot": "4.1.0", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -9717,8 +9720,8 @@ "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -9734,13 +9737,15 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.0", - "@vitest/browser-preview": "4.1.0", - "@vitest/browser-webdriverio": "4.1.0", - "@vitest/ui": "4.1.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", "happy-dom": "*", "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -9761,6 +9766,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -9951,9 +9962,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", "optional": true, diff --git a/frontend/package.json b/frontend/package.json index b763e62..06fa11f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,7 @@ "@react-spring/web": "^10.0.3", "@tanstack/react-virtual": "^3.13.22", "@use-gesture/react": "^10.3.1", - "axios": "1.13.6", + "axios": "1.15.0", "i18next": "^25.1.3", "react": "^18.3.1", "react-dom": "^18.3.1", 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/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 new file mode 100644 index 0000000..c7203db --- /dev/null +++ b/website/config/_default/menus.en.toml @@ -0,0 +1,19 @@ +[[main]] +name = "Home" +url = "/" +weight = 1 + +[[main]] +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/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..e75b991 --- /dev/null +++ b/website/config/_default/params.toml @@ -0,0 +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/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..f89f09b --- /dev/null +++ b/website/hugo.toml @@ -0,0 +1,22 @@ +baseURL = "https://sambee.net/" +title = "Sambee" +theme = "sambee" +defaultContentLanguage = "en" +enableRobotsTXT = true + +[pagination] +pagerSize = 10 + +[build] + [build.buildStats] + enable = true + +[outputs] +home = ["HTML"] +section = ["HTML"] +page = ["HTML"] + +[markup] + [markup.goldmark] + [markup.goldmark.renderer] + unsafe = true 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 new file mode 100644 index 0000000..48fec0b --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,1199 @@ +{ + "name": "sambee-website", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sambee-website", + "version": "0.1.0", + "devDependencies": { + "@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": { + "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/@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", + "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" + } + }, + "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 new file mode 100644 index 0000000..4ad2981 --- /dev/null +++ b/website/package.json @@ -0,0 +1,18 @@ +{ + "name": "sambee-website", + "private": true, + "version": "0.1.0", + "scripts": { + "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": { + "@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/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/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 new file mode 100644 index 0000000..1f1ee24 --- /dev/null +++ b/website/themes/sambee/layouts/_default/baseof.html @@ -0,0 +1,17 @@ +{{- $initialTheme := cond (eq site.Params.theme_default "dark") "dark" "" -}} + + + + {{ partial "essentials/head.html" . }} + {{ partialCached "essentials/style.html" . }} + + + {{ partial "essentials/header.html" . }} + {{ partial "search-modal.html" . }} +
+ {{ block "main" . }}{{ end }} +
+ {{ 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 new file mode 100644 index 0000000..1ca5508 --- /dev/null +++ b/website/themes/sambee/layouts/_default/list.html @@ -0,0 +1,10 @@ +{{ 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..1ca5508 --- /dev/null +++ b/website/themes/sambee/layouts/_default/single.html @@ -0,0 +1,10 @@ +{{ 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..754a39e --- /dev/null +++ b/website/themes/sambee/layouts/docs/list.html @@ -0,0 +1,67 @@ +{{ define "main" }} +{{ $currentVersion := hugo.Data.docs_versions.current }} +{{ $navData := index hugo.Data.docs_nav $currentVersion }} + +{{ if and (not .Params.product_version) (not .Params.book) }} +
+
+ Documentation +

{{ .Title }}

+
{{ .Content }}
+
+ {{ with $navData }} +
+ {{ range .books }} + + {{ end }} +
+ {{ end }} +
+{{ else if and .Params.product_version (not .Params.book) }} +
+
+ Version {{ .Params.product_version }} +

{{ .Title }}

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

{{ .title }}

+

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

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

{{ .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..793dae9 --- /dev/null +++ b/website/themes/sambee/layouts/docs/single.html @@ -0,0 +1,16 @@ +{{ define "main" }} +
+ +
+ {{ partial "docs/version-switcher.html" . }} +
+ Version {{ .Params.product_version }} + {{ .Params.book }} +
+

{{ .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..2aa1612 --- /dev/null +++ b/website/themes/sambee/layouts/home.html @@ -0,0 +1,54 @@ +{{ define "main" }} +{{ $currentVersion := hugo.Data.docs_versions.current }} +{{ $navData := index hugo.Data.docs_nav $currentVersion }} +
+
+ Sambee +

{{ .Title }}

+
+ {{ .Content }} +
+ +
+ +
+ +{{ 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 new file mode 100644 index 0000000..1566d5c --- /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 }} +
+
+ {{ with $bookPage }} + {{ .Title }} + {{ else }} + {{ .title }} + {{ end }} +
+ {{ range .sections }} +
+ {{ .title }} +
    + {{ range .doc_ids }} + {{ $docId := . }} + {{ $matches := where $bookPages "Params.doc_id" $docId }} + {{ if gt (len $matches) 0 }} + {{ $target := index $matches 0 }} +
  • + {{ if eq $target.RelPermalink $.RelPermalink }} + {{ $target.Title }} + {{ else }} + {{ $target.Title }} + {{ end }} +
  • + {{ end }} + {{ end }} +
+
+ {{ end }} +
+ {{ 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..3765d28 --- /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 }} +
diff --git a/website/themes/sambee/layouts/partials/essentials/footer.html b/website/themes/sambee/layouts/partials/essentials/footer.html new file mode 100644 index 0000000..514e734 --- /dev/null +++ b/website/themes/sambee/layouts/partials/essentials/footer.html @@ -0,0 +1,14 @@ +
+
+ +
+
diff --git a/website/themes/sambee/layouts/partials/essentials/head.html b/website/themes/sambee/layouts/partials/essentials/head.html new file mode 100644 index 0000000..78a5d8a --- /dev/null +++ b/website/themes/sambee/layouts/partials/essentials/head.html @@ -0,0 +1,17 @@ +{{ $pageTitle := site.Title }} +{{ if and .Title (ne .Title site.Title) }} + {{ $pageTitle = printf "%s | %s" .Title site.Title }} +{{ end }} + + +{{ $pageTitle }} + +{{ with .Permalink }} + +{{ end }} + + + +{{ with .Permalink }} + +{{ end }} diff --git a/website/themes/sambee/layouts/partials/essentials/header.html b/website/themes/sambee/layouts/partials/essentials/header.html new file mode 100644 index 0000000..e30804d --- /dev/null +++ b/website/themes/sambee/layouts/partials/essentials/header.html @@ -0,0 +1,53 @@ +
+ +
diff --git a/website/themes/sambee/layouts/partials/essentials/script.html b/website/themes/sambee/layouts/partials/essentials/script.html new file mode 100644 index 0000000..a3a29ad --- /dev/null +++ b/website/themes/sambee/layouts/partials/essentials/script.html @@ -0,0 +1,8 @@ +{{ $scripts := slice }} +{{ $scripts = $scripts | append (resources.Get "js/navigation.js") }} +{{ $scripts = $scripts | append (resources.Get "js/main.js") }} +{{ $scripts = $scripts | resources.Concat "js/script.js" }} +{{ if hugo.IsProduction }} + {{ $scripts = $scripts | minify | fingerprint }} +{{ end }} + diff --git a/website/themes/sambee/layouts/partials/essentials/style.html b/website/themes/sambee/layouts/partials/essentials/style.html new file mode 100644 index 0000000..66e06e0 --- /dev/null +++ b/website/themes/sambee/layouts/partials/essentials/style.html @@ -0,0 +1,16 @@ + + + +{{ $pf := hugo.Data.theme.fonts.font_family.primary }} +{{ $sf := hugo.Data.theme.fonts.font_family.secondary }} +{{ $tf := hugo.Data.theme.fonts.font_family.tertiary }} +{{ $fontUrl := printf "https://fonts.googleapis.com/css2?family=%s%s%s&display=swap" ($pf | safeURL) (cond (ne $sf "") (printf "&family=%s" ($sf | safeURL)) "") (cond (ne $tf "") (printf "&family=%s" ($tf | safeURL)) "") }} + + +{{ $mainCSS := resources.Get "css/main.css" }} +{{ $tailwindCSS := $mainCSS | css.TailwindCSS (dict "inlineImports" true) }} +{{ $styles := slice $tailwindCSS | resources.Concat "css/style.css" }} +{{ if hugo.IsProduction }} + {{ $styles = $styles | minify | fingerprint }} +{{ end }} + diff --git a/website/themes/sambee/layouts/partials/icons/chevron-right.html b/website/themes/sambee/layouts/partials/icons/chevron-right.html new file mode 100644 index 0000000..b50625c --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/chevron-right.html @@ -0,0 +1,12 @@ +{{- /* Chevron right icon */ -}} +{{- $baseClass := "size-[1em] inline-block align-[-0.125em]" -}} +{{- $class := .class | default $baseClass -}} +{{- if .rotate -}} + {{- $class = printf "%s rotate-%v" $class .rotate -}} +{{- end -}} + diff --git a/website/themes/sambee/layouts/partials/icons/clock.html b/website/themes/sambee/layouts/partials/icons/clock.html new file mode 100644 index 0000000..0a7de7c --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/clock.html @@ -0,0 +1,2 @@ +{{- /* Clock icon (Font Awesome 6 regular/clock) */ -}} + diff --git a/website/themes/sambee/layouts/partials/icons/folder.html b/website/themes/sambee/layouts/partials/icons/folder.html new file mode 100644 index 0000000..e1a4c11 --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/folder.html @@ -0,0 +1,2 @@ +{{- /* Folder icon (Font Awesome 6 regular/folder) */ -}} + diff --git a/website/themes/sambee/layouts/partials/icons/github.html b/website/themes/sambee/layouts/partials/icons/github.html new file mode 100644 index 0000000..8c5b595 --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/github.html @@ -0,0 +1,2 @@ +{{- $class := .class | default "size-5" -}} + diff --git a/website/themes/sambee/layouts/partials/icons/linkedin.html b/website/themes/sambee/layouts/partials/icons/linkedin.html new file mode 100644 index 0000000..4109205 --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/linkedin.html @@ -0,0 +1,2 @@ +{{- $class := .class | default "size-5" -}} + diff --git a/website/themes/sambee/layouts/partials/icons/rss.html b/website/themes/sambee/layouts/partials/icons/rss.html new file mode 100644 index 0000000..ba425d6 --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/rss.html @@ -0,0 +1,2 @@ +{{- $class := .class | default "size-5" -}} + diff --git a/website/themes/sambee/layouts/partials/icons/search.html b/website/themes/sambee/layouts/partials/icons/search.html new file mode 100644 index 0000000..4aa7fe2 --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/search.html @@ -0,0 +1,2 @@ +{{- /* Magnifying glass icon (Font Awesome 6 solid/search) */ -}} + diff --git a/website/themes/sambee/layouts/partials/icons/tag.html b/website/themes/sambee/layouts/partials/icons/tag.html new file mode 100644 index 0000000..b5e59fb --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/tag.html @@ -0,0 +1,2 @@ +{{- /* Tag icon (Heroicons outline/tag) */ -}} + diff --git a/website/themes/sambee/layouts/partials/icons/user.html b/website/themes/sambee/layouts/partials/icons/user.html new file mode 100644 index 0000000..70d9949 --- /dev/null +++ b/website/themes/sambee/layouts/partials/icons/user.html @@ -0,0 +1,2 @@ +{{- /* User circle icon (Font Awesome 6 regular/circle-user) */ -}} + diff --git a/website/themes/sambee/layouts/partials/logo.html b/website/themes/sambee/layouts/partials/logo.html new file mode 100644 index 0000000..628e0ed --- /dev/null +++ b/website/themes/sambee/layouts/partials/logo.html @@ -0,0 +1,23 @@ +{{/* +Logo Partial + +Displays logo-black.svg (light mode) and logo-white.svg (dark mode). +Falls back to site.Title text if logos are missing. +*/}} + +{{ $logoBlack := resources.Get "images/logo-black.svg" }} +{{ $logoWhite := resources.Get "images/logo-white.svg" }} + +{{ if $logoBlack }} +{{ site.Title }} +{{ end }} + +{{ if $logoWhite }} + +{{ end }} + +{{ if not (or $logoBlack $logoWhite) }} +{{ site.Title }} +{{ end }} \ No newline at end of file diff --git a/website/themes/sambee/layouts/partials/search-modal.html b/website/themes/sambee/layouts/partials/search-modal.html new file mode 100644 index 0000000..391fe8c --- /dev/null +++ b/website/themes/sambee/layouts/partials/search-modal.html @@ -0,0 +1,348 @@ +{{- /* + Search Modal with Pagefinö Backend + + Uses the theme's custom UI with Pagefind's search API. + - Lazy-loads Pagefind JS only when modal opens + - Full control over result rendering + - Keyboard navigation: arrows to navigate, Enter to select, ESC to close + + Requires: Run `npx pagefind --site public` after Hugo build +*/ -}} + +{{ with site.Params.search }} +{{ if .enable }} + + + +{{ end }} +{{ end }}