Skip to content

Commit 80daf8e

Browse files
authored
feat(site): split portfolio root and docs-style project sections (#4)
1 parent da70212 commit 80daf8e

29 files changed

Lines changed: 639 additions & 44 deletions

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "themes/hugo-coder"]
22
path = themes/hugo-coder
33
url = https://github.com/luizdepra/hugo-coder.git
4+
[submodule "themes/hextra"]
5+
path = themes/hextra
6+
url = https://github.com/imfing/hextra.git

assets/css/docs-overrides.css

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
.content :where(a):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
2+
text-decoration: none;
3+
}
4+
5+
.content .subheading-anchor {
6+
margin-left: 0.35rem;
7+
margin-right: 0.35rem;
8+
opacity: 0;
9+
transition: opacity 0.2s ease;
10+
}
11+
12+
.content h2:hover .subheading-anchor,
13+
.content h3:hover .subheading-anchor,
14+
.content h4:hover .subheading-anchor,
15+
.content h5:hover .subheading-anchor,
16+
.content h6:hover .subheading-anchor,
17+
.content .subheading-anchor:focus-visible,
18+
.content span:target + .subheading-anchor {
19+
opacity: 1;
20+
}
21+
22+
.content .subheading-anchor::after {
23+
content: "#";
24+
color: #9ca3af;
25+
font-size: 0.8em;
26+
font-weight: 500;
27+
}
28+
29+
.dark .content .subheading-anchor::after {
30+
color: #6b7280;
31+
}
32+
33+
.hextra-footer {
34+
padding-top: 2.5rem;
35+
padding-bottom: 2.5rem;
36+
}
37+
38+
.hextra-footer a {
39+
color: inherit;
40+
text-decoration: underline;
41+
text-underline-offset: 0.15em;
42+
}
43+
44+
.hextra-footer a:hover,
45+
.hextra-footer a:focus {
46+
color: var(--hx-color-primary-600);
47+
}
48+
49+
.dark .hextra-footer a:hover,
50+
.dark .hextra-footer a:focus {
51+
color: var(--hx-color-primary-500);
52+
}

assets/js/coder.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
const body = document.body;
2+
const darkModeToggle = document.getElementById("dark-mode-toggle");
3+
4+
function rememberTheme(theme) {
5+
localStorage.setItem("colorscheme", theme);
6+
localStorage.setItem("color-theme", theme);
7+
}
8+
9+
function setTheme(theme) {
10+
const resolved = theme === "dark" ? "dark" : "light";
11+
const inverse = resolved === "dark" ? "light" : "dark";
12+
13+
body.classList.remove("colorscheme-auto");
14+
body.classList.remove("colorscheme-" + inverse);
15+
body.classList.add("colorscheme-" + resolved);
16+
document.documentElement.style["color-scheme"] = resolved;
17+
18+
function waitForElm(selector) {
19+
return new Promise((resolve) => {
20+
if (document.querySelector(selector)) {
21+
resolve(document.querySelector(selector));
22+
return;
23+
}
24+
25+
const observer = new MutationObserver(() => {
26+
if (document.querySelector(selector)) {
27+
resolve(document.querySelector(selector));
28+
observer.disconnect();
29+
}
30+
});
31+
32+
observer.observe(document.body, {
33+
childList: true,
34+
subtree: true,
35+
});
36+
});
37+
}
38+
39+
const utterancesMessage = {
40+
type: "set-theme",
41+
theme: resolved === "dark" ? "github-dark" : "github-light",
42+
};
43+
44+
waitForElm(".utterances-frame").then((iframe) => {
45+
iframe.contentWindow.postMessage(utterancesMessage, "https://utteranc.es");
46+
});
47+
48+
function sendMessage(message) {
49+
const iframe = document.querySelector("iframe.giscus-frame");
50+
if (!iframe) return;
51+
iframe.contentWindow.postMessage({ giscus: message }, "https://giscus.app");
52+
}
53+
54+
sendMessage({
55+
setConfig: {
56+
theme: resolved,
57+
},
58+
});
59+
60+
document.dispatchEvent(new Event("themeChanged"));
61+
}
62+
63+
const storedTheme =
64+
localStorage.getItem("colorscheme") ||
65+
localStorage.getItem("color-theme") ||
66+
(body.classList.contains("colorscheme-dark") ? "dark" : "light");
67+
68+
setTheme(storedTheme);
69+
rememberTheme(storedTheme);
70+
71+
if (darkModeToggle) {
72+
darkModeToggle.addEventListener("click", () => {
73+
const theme = body.classList.contains("colorscheme-dark") ? "light" : "dark";
74+
setTheme(theme);
75+
rememberTheme(theme);
76+
});
77+
}
78+
79+
document.addEventListener("DOMContentLoaded", () => {
80+
const node = document.querySelector(".preload-transitions");
81+
if (node) {
82+
node.classList.remove("preload-transitions");
83+
}
84+
});
85+
86+
window.addEventListener("storage", (event) => {
87+
if (event.key !== "colorscheme" && event.key !== "color-theme") {
88+
return;
89+
}
90+
91+
const theme = event.newValue === "dark" ? "dark" : "light";
92+
setTheme(theme);
93+
rememberTheme(theme);
94+
});

assets/js/core/theme.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
(function () {
2+
const themeToggleButtons = document.querySelectorAll(".hextra-theme-toggle");
3+
4+
function normalizeTheme(theme) {
5+
return theme === "dark" ? "dark" : "light";
6+
}
7+
8+
function persistTheme(theme) {
9+
localStorage.setItem("color-theme", theme);
10+
localStorage.setItem("colorscheme", theme);
11+
}
12+
13+
function applyTheme(theme) {
14+
const normalized = normalizeTheme(theme);
15+
16+
themeToggleButtons.forEach((btn) => {
17+
if (btn.parentElement) {
18+
btn.parentElement.dataset.theme = normalized;
19+
}
20+
});
21+
22+
persistTheme(normalized);
23+
return normalized;
24+
}
25+
26+
function switchTheme(theme) {
27+
const normalized = normalizeTheme(theme);
28+
setTheme(normalized);
29+
applyTheme(normalized);
30+
}
31+
32+
const colorTheme = normalizeTheme(localStorage.getItem("color-theme") || localStorage.getItem("colorscheme") || "light");
33+
switchTheme(colorTheme);
34+
35+
themeToggleButtons.forEach((toggler) => {
36+
toggler.addEventListener("click", (e) => {
37+
e.preventDefault();
38+
39+
const current = toggler.parentElement && toggler.parentElement.dataset.theme === "dark" ? "dark" : "light";
40+
const next = current === "dark" ? "light" : "dark";
41+
switchTheme(next);
42+
});
43+
});
44+
45+
window.addEventListener("storage", (event) => {
46+
if (event.key !== "color-theme" && event.key !== "colorscheme") {
47+
return;
48+
}
49+
50+
const theme = normalizeTheme(event.newValue);
51+
setTheme(theme);
52+
applyTheme(theme);
53+
});
54+
})();

assets/js/docs-overrides.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
document.addEventListener("DOMContentLoaded", () => {
2+
const footer = document.querySelector(".hextra-footer");
3+
if (!footer) {
4+
return;
5+
}
6+
7+
footer.querySelectorAll("a").forEach((link) => {
8+
link.style.textDecoration = "underline";
9+
link.style.textUnderlineOffset = "0.15em";
10+
});
11+
});

assets/js/head/theme.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
function setTheme(theme) {
2+
document.documentElement.classList.remove("light", "dark");
3+
4+
if (theme !== "light" && theme !== "dark") {
5+
theme = "light";
6+
}
7+
8+
document.documentElement.classList.add(theme);
9+
document.documentElement.style.colorScheme = theme;
10+
}
11+
12+
const savedTheme =
13+
localStorage.getItem("color-theme") ||
14+
localStorage.getItem("colorscheme") ||
15+
"light";
16+
17+
const initialTheme = savedTheme === "dark" ? "dark" : "light";
18+
setTheme(initialTheme);
19+
localStorage.setItem("color-theme", initialTheme);
20+
localStorage.setItem("colorscheme", initialTheme);

content/about.md

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,21 @@ title = "About"
33
slug = "about"
44
+++
55

6-
## Why Spendly?
6+
I am Lallu Anthoor, a software developer focused on building useful products with clear UX, reliable architecture, and practical constraints.
77

8-
Let's be honest - most finance apps on the Play Store are either drowning in ads, demand a hefty subscription fee, or overcomplicate something that should be simple: tracking where your money goes.
8+
I work across Android and Python, with a strong preference for tools that keep users in control of their data and workflow.
99

10-
Spendly is my answer to that frustration. A no-nonsense personal finance tracker built for individuals who value their privacy and simplicity.
10+
## Projects I maintain
1111

12-
## What Makes It Different?
12+
- **[Spendly](/spendly/):** an offline-first personal finance app for Android built with Kotlin and Jetpack Compose.
13+
- **[Plottini](/plottini/):** a graph builder for publication-quality plots from TSV data, built with Python, Streamlit, and matplotlib.
1314

14-
- **Fully Offline** - Your data never leaves your device. No servers, no cloud sync, no tracking.
15-
- **Privacy First** - What you spend is nobody's business but yours.
16-
- **Zero Ads, Zero Bloat** - Just the features you need, nothing more.
17-
- **Own Your Data** - Import and export via JSON. Your data, your control.
15+
## How I work
1816

19-
## Current Status
17+
- Build from first principles and keep complexity intentional.
18+
- Prioritize maintainability, testing, and documentation as part of shipping.
19+
- Keep feedback loops short with clear releases and changelogs.
2020

21-
Spendly is currently in development. I'm focused on building something useful without the usual baggage that comes with finance apps.
21+
## Contact
2222

23-
## Who's Behind This?
24-
25-
Hi, I'm [Lallu Anthoor](https://linkedin.com/in/lallu). I built Spendly because I got tired of choosing between privacy and functionality in personal finance apps.
26-
27-
## Get Involved
28-
29-
Want to try it out or share feedback? Download the app and let me know what you think. Your input helps shape what Spendly becomes.
23+
- GitHub: [@lanthoor](https://github.com/lanthoor)

content/plottini/_index.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
+++
2+
title = "Plottini"
3+
slug = "plottini"
4+
type = "docs"
5+
6+
[[cascade]]
7+
type = "docs"
8+
+++
9+
10+
Plottini is a graph builder for creating publication-quality plots from TSV data without writing plotting code.
11+
12+
It combines a Streamlit-based interface with a desktop wrapper and matplotlib rendering, so researchers and technical users can go from raw tabular data to export-ready figures quickly.
13+
14+
## Who it is for
15+
16+
Plottini is designed for researchers, scientists, students, and engineers who want precise control over charts without rebuilding plotting logic for every dataset.
17+
18+
## What you can do
19+
20+
- Load one or more TSV files with configurable headers and comment delimiters
21+
- Build line, scatter, bar, histogram, pie, polar, box, violin, area, and other plot types
22+
- Apply transformations such as log, sqrt, power, and trigonometric functions
23+
- Define derived columns with safe mathematical expressions
24+
- Filter rows by value ranges before plotting
25+
- Align multiple files by a common column and overlay series
26+
- Use secondary Y-axis and configure layout, labels, legends, and grid
27+
- Preview changes live while tuning the figure
28+
29+
## Export options
30+
31+
- PNG, SVG, PDF, and EPS output
32+
- Configurable DPI for high-resolution raster export
33+
- Vector output for publication and presentation workflows
34+
35+
## Technical stack
36+
37+
- Python 3.10+
38+
- Streamlit for interactive UI
39+
- PyWebView for desktop packaging
40+
- matplotlib for rendering
41+
- NumPy and Click for computation and CLI ergonomics
42+
43+
## Quick start
44+
45+
```bash
46+
pip install plottini
47+
plottini
48+
```
49+
50+
## Project links
51+
52+
- [Repository](https://github.com/lanthoor/plottini)
53+
- [Issues](https://github.com/lanthoor/plottini/issues)

content/spendly/_index.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
+++
2+
title = "Spendly"
3+
slug = "spendly"
4+
type = "docs"
5+
next = "/spendly/about/"
6+
7+
[[cascade]]
8+
type = "docs"
9+
+++
10+
11+
Spendly is an offline-first personal finance tracker for Android.
12+
13+
Built with Kotlin and Jetpack Compose, it focuses on practical money tracking without ads, cloud lock-in, or unnecessary complexity.
14+
15+
## Highlights
16+
17+
- Offline-only architecture with local Room database storage
18+
- Expense and income tracking with accounts, categories, and recurring entries
19+
- SMS transaction auto-detection for Indian banks, UPI, and credit cards
20+
- Budgets with threshold notifications and historical analytics
21+
- JSON import/export for full data portability
22+
- Biometric app lock with configurable timeout
23+
24+
## Current status
25+
26+
Spendly is currently in beta (`0.8.0-beta`) with core features complete and active development continuing.
27+
28+
## Learn more
29+
30+
- [About Spendly](/spendly/about/)
31+
- [Spendly Privacy Policy](/spendly/privacy/)
32+
- [Spendly Changelog](/spendly/changelog/)
33+
- [GitHub Repository](https://github.com/lanthoor/spendly)

content/spendly/about.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
+++
2+
title = "About Spendly"
3+
slug = "about"
4+
prev = "/spendly/"
5+
next = "/spendly/privacy/"
6+
+++
7+
8+
## Why Spendly?
9+
10+
Most personal finance apps are overloaded with ads, subscriptions, or cloud dependencies.
11+
12+
Spendly is built as a clean alternative: track money clearly, keep data local, and stay in control.
13+
14+
## What makes it different
15+
16+
- **Offline first:** no account required and no mandatory cloud sync
17+
- **Privacy minded:** financial data remains on-device
18+
- **Practical workflows:** recurring entries, receipt attachments, filters, and category analytics
19+
- **Data ownership:** full JSON backup and restore support
20+
21+
## Built for day-to-day use
22+
23+
Spendly is designed to be useful every day, with clear transaction flows, budget monitoring, and quick insight into spending trends.
24+
25+
## Project links
26+
27+
- [Repository](https://github.com/lanthoor/spendly)
28+
- [Changelog](/spendly/changelog/)
29+
- [Privacy Policy](/spendly/privacy/)

0 commit comments

Comments
 (0)