Compare commits

..

10 Commits

Author SHA1 Message Date
SauravDhakal
3a1d508c65 fix: Search
Some checks failed
Deploy Hugo Site / deploy (push) Has been cancelled
2026-04-01 12:11:17 +05:45
SauravDhakal
0c4e6715de fix: Layout and theme 2026-04-01 10:33:28 +05:45
SauravDhakal
38baeb86e8 fix: theme a bit 2026-04-01 09:14:17 +05:45
SauravDhakal
df6f488ccd feat: Init with clean layout 2026-04-01 08:06:08 +05:45
sauravdhakal12
1206387246 fix: link 2026-01-19 21:51:58 +05:45
sauravdhakal12
9995edb14d feat: New post 2026-01-19 21:37:20 +05:45
sauravdhakal12
8a5439d2aa fix: gsite 2025-12-29 21:50:44 +05:45
sauravdhakal12
e3224e321e fix: gtag 2025-12-29 21:48:57 +05:45
sauravdhakal12
1c47537b54 fix: build + build command change 2025-12-29 21:36:37 +05:45
sauravdhakal12
c940743fd8 fix: baseUrl set 2025-12-29 21:33:18 +05:45
282 changed files with 1466 additions and 1990 deletions

View File

@@ -20,7 +20,7 @@ jobs:
extended: true
- name: Build site
run: hugo --minify
run: hugo build --minify
- name: Deploy via rsync
uses: appleboy/scp-action@v0.1.7

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# Hugo build output
public/
resources/_gen/
# Hugo lock file
.hugo_build.lock
# OS files
.DS_Store
Thumbs.db
# Editor files
*.swp
*.swo
*~
.idea/
.vscode/
# Environment files
.env
.env.local

541
CUSTOMIZATION.md Normal file
View File

@@ -0,0 +1,541 @@
# Hugo + PaperMod Customization Guide
This guide covers how to customize your Hugo portfolio site using the PaperMod theme.
---
## Table of Contents
1. [Project Structure](#project-structure)
2. [Development Workflow](#development-workflow)
3. [Configuration (hugo.yaml)](#configuration-hugoyaml)
4. [Adding Content](#adding-content)
5. [Creating New Sections](#creating-new-sections)
6. [Customizing Styles (CSS)](#customizing-styles-css)
7. [Overriding Layouts](#overriding-layouts)
8. [Common Customizations](#common-customizations)
9. [Deployment](#deployment)
---
## Project Structure
```
MyPortfolio/
├── archetypes/ # Templates for new content
├── assets/
│ └── css/
│ └── extended/
│ └── custom.css # YOUR custom CSS (this gets merged)
├── content/
│ ├── posts/ # Long-form articles
│ │ └── _index.md # Section config
│ ├── notes/ # Short notes/TILs
│ │ └── _index.md # Section config
│ └── search.md # Search page
├── layouts/
│ ├── index.html # Custom home page (no recent posts)
│ ├── partials/ # Override theme partials
│ │ ├── footer.html # Custom footer
│ │ └── templates/
│ │ └── schema_json.html # Fixed JSON-LD schema
│ ├── posts/
│ │ └── list.html # Custom posts list (compact style)
│ └── notes/
│ └── list.html # Custom notes list (compact style)
├── static/
│ └── files/ # Static files (CV.pdf, images, etc.)
├── themes/
│ └── PaperMod/ # Theme (git submodule - DON'T edit)
├── hugo.yaml # Main configuration
└── .gitignore # Ignores public/, etc.
```
### Key Principle: Never Edit the Theme Directly
The `themes/PaperMod/` folder is a git submodule. To customize:
- **CSS**: Add to `assets/css/extended/custom.css`
- **Layouts**: Copy the file from `themes/PaperMod/layouts/` to `layouts/` and modify
- **Config**: Everything in `hugo.yaml`
---
## Development Workflow
### Local Development
```bash
# Start dev server (watches for changes)
hugo serve
# With drafts visible
hugo serve -D
# Build for production (outputs to public/)
hugo build --minify
```
### Why `public/` is Gitignored
- `public/` is generated output - it's rebuilt fresh every time
- Your CI/CD (GitHub Actions) builds it during deployment
- Never commit build artifacts to git
### Git Workflow
```bash
# After making changes
git add .
git commit -m "Add new post about X"
git push origin master # Triggers deployment
```
---
## Configuration (hugo.yaml)
### Key Sections
#### Site Basics
```yaml
baseURL: "/" # Use "/" for relative URLs (works on localhost + prod)
relativeURLs: true # Makes all URLs relative
title: Saurav Dhakal
languageCode: en-us
theme: ["PaperMod"]
```
#### Menu (Header Navigation)
```yaml
menu:
main:
- identifier: posts # Internal ID
name: Posts # Display name
url: /posts/ # URL path
weight: 10 # Order (lower = first)
- identifier: notes
name: Notes
url: /notes/
weight: 20
- identifier: tags
name: Tags
url: /tags/
weight: 30
- identifier: search
name: Search
url: /search/
weight: 40
```
#### Home Page Info
```yaml
params:
homeInfoParams:
Title: "Hi there 👋, I'm Saurav!"
Content: >
I'm a software engineer...
```
#### Social Icons
```yaml
params:
socialIcons:
- name: github
url: "https://github.com/yourusername"
- name: linkedin
url: "https://linkedin.com/in/yourusername"
- name: x
url: "https://x.com/yourusername"
```
Available icons: github, linkedin, x, twitter, email, rss, youtube, instagram, facebook, stackoverflow, etc.
---
## Adding Content
### New Post
```bash
hugo new posts/my-new-post.md
```
Or manually create `content/posts/my-new-post.md`:
```markdown
---
title: "My New Post"
date: 2025-08-20
summary: "A brief description for the list view"
tags: ["tag1", "tag2"]
categories: ["Category"]
draft: false
ShowToc: true
TocOpen: false
---
Your content here...
```
### New Note
Create `content/notes/my-note.md`:
```markdown
---
title: "Quick TIL"
date: 2025-08-20
summary: "Today I learned about X"
tags: ["til"]
---
Short content here...
```
### Front Matter Options
| Field | Description |
|-------|-------------|
| `title` | Post title |
| `date` | Publication date (YYYY-MM-DD) |
| `summary` | Short description for list views |
| `tags` | Array of tags |
| `categories` | Array of categories |
| `draft` | If `true`, won't be published |
| `ShowToc` | Show table of contents |
| `TocOpen` | TOC expanded by default |
| `ShowReadingTime` | Override global setting |
| `ShowWordCount` | Override global setting |
| `cover.image` | Cover image path |
---
## Creating New Sections
### Example: Adding a "Projects" Section
1. **Add to menu** in `hugo.yaml`:
```yaml
menu:
main:
# ... existing items
- identifier: projects
name: Projects
url: /projects/
weight: 25
```
2. **Create content directory**:
```bash
mkdir -p content/projects
```
3. **Create section index** `content/projects/_index.md`:
```markdown
---
title: "Projects"
description: "Things I've built"
---
```
4. **Add project pages** `content/projects/my-project.md`:
```markdown
---
title: "My Cool Project"
date: 2025-01-15
summary: "A brief description"
tags: ["golang", "cli"]
---
Project description...
```
5. **(Optional) Custom layout** - If you want projects to look different, create `layouts/projects/list.html`
---
## Customizing Styles (CSS)
### Where to Add CSS
**Only edit**: `assets/css/extended/custom.css`
PaperMod automatically includes this file. You don't need to import it anywhere.
### CSS Variables (Theme Colors)
PaperMod uses CSS variables. Override them in your custom.css:
```css
:root {
/* Light mode */
--primary: #282828; /* Main text */
--secondary: #3c3836; /* Secondary text */
--tertiary: rgb(214, 214, 214); /* Borders, etc */
--theme: rgb(255, 255, 255); /* Background */
--entry: rgb(255, 255, 255); /* Card background */
}
:root[data-theme="dark"] {
/* Dark mode */
--primary: #fbf1c7;
--secondary: #ebdbb2;
--theme: #181818;
--entry: rgb(46, 46, 51);
}
```
### Common CSS Customizations
#### Change fonts
```css
@import url("https://fonts.googleapis.com/css2?family=Your+Font&display=swap");
body {
font-family: "Your Font", sans-serif;
}
```
#### Style links
```css
main a {
text-decoration: underline;
text-decoration-color: var(--green);
}
main a:hover {
background-color: var(--green);
color: white;
}
```
#### Customize post cards
```css
main .post-entry {
border: 2px solid #383838;
background-color: var(--entry);
border-radius: 8px;
}
```
---
## Overriding Layouts
### How Layout Override Works
Hugo looks for templates in this order:
1. `layouts/` (your overrides)
2. `themes/PaperMod/layouts/` (theme defaults)
### To Override a Template
1. Find the file in `themes/PaperMod/layouts/`
2. Copy it to the same path in `layouts/`
3. Modify your copy
### Common Files to Override
| File | Purpose |
|------|---------|
| `layouts/partials/header.html` | Site header/nav |
| `layouts/partials/footer.html` | Site footer |
| `layouts/partials/post_meta.html` | Post metadata (date, read time) |
| `layouts/_default/list.html` | List pages (posts, tags) |
| `layouts/_default/single.html` | Individual post/page |
### Example: Simpler Footer
Your `layouts/partials/footer.html` already overrides the theme's footer.
### Section-Specific Layouts
Create `layouts/SECTION_NAME/list.html` for a custom list layout for that section.
**Current custom layouts:**
- `layouts/index.html` - Home page with just intro + social icons (no recent posts)
- `layouts/posts/list.html` - Compact list: `# Title` + date + 2-line description
- `layouts/notes/list.html` - Compact list: `# Title` + date (no description)
---
## Common Customizations
### Disable Reading Time for a Section
In the section's `_index.md`:
```markdown
---
title: "Notes"
ShowReadingTime: false
---
```
Or per-post in front matter.
### Add a Static Page (About, Contact)
Create `content/about.md`:
```markdown
---
title: "About"
layout: "single"
url: "/about/"
---
About me content...
```
Add to menu:
```yaml
menu:
main:
- identifier: about
name: About
url: /about/
weight: 50
```
### Add Favicon
1. Place favicon files in `static/`:
- `static/favicon.ico`
- `static/favicon-16x16.png`
- `static/favicon-32x32.png`
- `static/apple-touch-icon.png`
2. Update `hugo.yaml`:
```yaml
params:
assets:
favicon: "/favicon.ico"
favicon16x16: "/favicon-16x16.png"
favicon32x32: "/favicon-32x32.png"
apple_touch_icon: "/apple-touch-icon.png"
```
### Enable Comments (Disqus)
```yaml
params:
comments: true
disqusShortname: "your-disqus-shortname"
```
### Add Google Analytics
Already configured in your `hugo.yaml`:
```yaml
googleAnalytics: "G-XXXXXXXXXX"
```
---
## Deployment
### Current Setup (GitHub Actions → SSH/SCP)
Your `.github/workflows/deploy.yml`:
1. Triggers on push to `master`
2. Builds site with `hugo build --minify`
3. Deploys `public/` via SCP to your server
### Required GitHub Secrets
Set these in your repo's Settings → Secrets:
- `SSH_HOST` - Your server hostname/IP
- `SSH_USER` - SSH username
- `SSH_KEY` - Private SSH key
- `SSH_PORT` - SSH port (usually 22)
### Alternative: GitHub Pages
If you want to use GitHub Pages instead:
1. Change workflow to use `peaceiris/actions-gh-pages`
2. Set `baseURL` to `https://yourusername.github.io/repo-name/`
### Alternative: Netlify/Vercel
These platforms auto-detect Hugo and build for you:
1. Connect your GitHub repo
2. Set build command: `hugo --minify`
3. Set publish directory: `public`
---
## Quick Reference
### Useful Commands
```bash
# Local dev
hugo serve -D # Serve with drafts
# Create content
hugo new posts/title.md # New post
hugo new notes/title.md # New note
# Build
hugo build --minify # Production build
# Debug
hugo config # Show full config
hugo list all # List all content
```
### File Locations
| What | Where |
|------|-------|
| Config | `hugo.yaml` |
| Custom CSS | `assets/css/extended/custom.css` |
| Posts | `content/posts/` |
| Notes | `content/notes/` |
| Static files | `static/` |
| Layout overrides | `layouts/` |
---
## Troubleshooting
### Links Go to Production URL in Dev
Fixed by setting:
```yaml
baseURL: "/"
relativeURLs: true
```
### Changes Not Showing
1. Hard refresh browser (Ctrl+Shift+R)
2. Clear `public/` folder: `rm -rf public/`
3. Restart hugo serve
### Theme Not Loading
```bash
git submodule update --init --recursive
```
### Search Not Working
Ensure `hugo.yaml` has:
```yaml
outputs:
home:
- HTML
- RSS
- JSON # Required for search
```
---
## Resources
- [Hugo Documentation](https://gohugo.io/documentation/)
- [PaperMod Wiki](https://github.com/adityatelange/hugo-PaperMod/wiki)
- [PaperMod Demo](https://adityatelange.github.io/hugo-PaperMod/)

View File

@@ -50,63 +50,124 @@
}
:root[data-theme="dark"] {
--theme: #181818;
--entry: rgb(46, 46, 51);
--primary: #fbf1c7;
--secondary: #ebdbb2;
--tertiary: rgb(65, 66, 68);
--content: rgb(196, 196, 197);
--code-block-bg: rgb(46, 46, 51);
--code-bg: rgb(55, 56, 62);
--border: rgb(151, 51, 51);
--theme: #1d2021;
--entry: #282828;
--primary: #ebdbb2;
--secondary: #a89984;
--tertiary: #3c3836;
--content: #ebdbb2;
--code-block-bg: #282828;
--code-bg: #3c3836;
--border: #3c3836;
color-scheme: dark;
--post-entry-bg: #181818; /* bg1 */
--post-entry-bg: #282828;
--post-entry-fg: #ebdbb2;
--green: #b8bb26;
--greenl: #8ec07c;
}
body {
font-family: "Space Grotesk", sans-serif;
}
nav #menu span {
padding: 0px 2px;
text-decoration: underline;
text-decoration-thickness: 2px;
text-decoration-color: var(--green);
font-family: "Space Grotesk", monospace;
}
/* *
* NAV SECTION
* UNIVERSAL HOVER ANIMATION (Fill-up Effect)
* Target: Nav spans, main links (except entries), social links, and share icons.
* */
nav #menu span:hover {
nav #menu a span {
position: relative;
text-decoration: none !important;
color: inherit;
transition: color 0.3s ease;
z-index: 1;
padding: 8px; /* Reduced vertical padding */
}
.entry-content {
color: var(--primary)
}
main a:not(.entry-link, .anchor),
.social-icons a,
.share-buttons a,
.note-item a {
position: relative;
text-decoration: none !important;
color: inherit;
transition: color 0.3s ease;
z-index: 1;
display: inline-block;
}
/* Note items are full-width flex containers */
.note-item a {
display: flex;
width: 100%;
padding: 8px 12px;
margin: 0 -12px; /* Offset padding for full-row effect */
}
/* The fill background */
nav #menu a span::before,
main a:not(.entry-link, .anchor)::before,
.social-icons a::before,
.share-buttons a::before,
.note-item a::before {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px; /* Static underline state */
background-color: var(--green);
color: white;
z-index: -1;
transition: height 0.3s cubic-bezier(0.19, 1, 0.22, 1);
}
nav #menu .active {
border: 0px;
color: var(--green);
text-decoration: underline;
text-decoration-thickness: 2px;
text-decoration-color: var(--green);
/* Notes list starts with 0 height to stay clean */
.note-item a::before,
.share-buttons a::before {
height: 0;
}
/* *
* MAIN SECTION
* */
main a {
text-decoration: underline;
text-decoration-thickness: 2px;
text-decoration-color: var(--green);
/* Hover state: Fill up the background */
nav #menu a:hover span::before,
main a:hover:not(.entry-link, .anchor)::before,
.social-icons a:hover::before,
.share-buttons a:hover::before,
.note-item a:hover::before {
height: 100%;
}
main a:hover:not(.entry-link) {
background-color: var(--green);
color: white;
/* CONTRAST FIX: Switch text/icon color on hover */
nav #menu a:hover span,
main a:hover:not(.entry-link, .anchor),
.social-icons a:hover,
.share-buttons a:hover,
#searchResults a:hover,
.note-item a:hover .note-title,
.note-item a:hover .note-date {
color: var(--theme) !important;
}
/* Icon specific color switch */
.social-icons a:hover svg,
.share-buttons a:hover svg {
fill: var(--theme) !important;
stroke: var(--theme) !important;
}
/* Active Nav State - remain thick underline */
nav #menu .active span::before {
height: 4px;
}
nav #menu .active a:hover span::before {
height: 100%;
}
main h1 {
@@ -117,12 +178,64 @@ main h1 {
.toc,
.post-tags a {
border: 0px;
background: transparent;
}
.post-tags a {
background: var(--tertiary);
color: var(--primary);
padding: 0px 8px;
border-radius: 0px;
transition: all 0.3s ease;
}
.post-tags a:hover {
background: var(--green);
color: var(--theme);
transform: translateY(-2px);
}
.share-buttons {
margin-top: 40px;
padding: 20px 0;
border-top: 1px solid var(--tertiary);
display: flex;
justify-content: center;
gap: 12px;
}
.share-buttons a {
padding: 10px;
border-radius: var(--radius);
}
.anchor {
text-decoration: none;
}
.breadcrumbs {
margin-bottom: 2rem;
font-size: 0.9rem;
color: var(--secondary);
}
.post-header .post-title {
font-size: 3rem;
margin-bottom: 0.5rem;
color: var(--green);
font-weight: 700;
line-height: 1.2;
}
.post-description {
font-style: italic;
font-size: 1.2rem;
margin-bottom: 1.5rem;
color: var(--secondary);
line-height: 1.6;
}
/* main h1::before { */
/* content: "#"; */
/* color: var(--red); */
@@ -134,8 +247,248 @@ main h1 {
/* } */
main .post-entry {
border: 0px;
border: 1px solid var(--tertiary);
background-color: var(--post-entry-bg);
color: var(--post-entry-fg);
border: 2px solid #383838;
transition: transform 0.2s ease, border-color 0.2s ease;
}
main .post-entry:hover {
transform: translateY(-2px);
border-color: var(--green);
}
/* *
* NOTES SECTION - Compact list style
* */
.notes-list {
list-style: none;
padding: 0;
margin: 0;
}
.note-item {
padding: 12px 0;
border-bottom: 1px solid var(--tertiary);
}
.note-item:last-child {
border-bottom: none;
}
.note-item a:hover .note-title,
.note-item a:hover .note-date {
color: var(--theme) !important;
}
.note-title {
font-size: 1.1rem;
font-weight: 500;
color: var(--primary);
}
.note-date {
font-size: 0.85rem;
color: var(--secondary);
white-space: nowrap;
}
.note-summary {
margin: 4px 0 0 0;
font-size: 0.96rem;
color: var(--secondary);
line-height: 1.5;
}
.delimiter {
margin: 0 10px;
font-weight: 800;
opacity: 0.5;
}
/* *
* POSTS SECTION - Same compact style as notes but with description
* */
.posts-list {
list-style: none;
padding: 0;
margin: 0;
}
.post-item {
padding: 12px 0;
border-bottom: 1px solid var(--tertiary);
}
.post-item:last-child {
border-bottom: none;
}
.post-item a {
position: relative;
display: flex;
justify-content: space-between;
align-items: baseline;
text-decoration: none !important;
gap: 16px;
padding: 8px 12px;
margin: 0 -12px;
color: inherit;
transition: color 0.3s ease;
z-index: 1;
width: 100%;
}
.post-item a::before {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 0;
background-color: var(--green);
z-index: -1;
transition: height 0.3s cubic-bezier(0.19, 1, 0.22, 1);
}
.post-item a:hover::before {
height: 100%;
}
.post-item a:hover .post-title,
.post-item a:hover .post-date {
color: var(--theme) !important;
}
.post-title {
font-size: 1.1rem;
font-weight: 500;
color: var(--primary);
}
.post-date {
font-size: 0.85rem;
color: var(--secondary);
white-space: nowrap;
}
.post-summary {
margin: 4px 0 0 0;
font-size: 0.96rem;
color: var(--secondary);
line-height: 1.5;
}
/* Common list styles */
.note-item a,
.post-item a {
display: flex !important;
justify-content: flex-start;
align-items: center;
}
/* *
* POST CONTENT SPACINGS
* */
.post-content {
font-size: 1.1rem;
line-height: 1.7;
color: var(--primary);
}
.post-content p,
.post-content ol,
.post-content ul,
.post-content dl {
margin-bottom: 1.6em;
}
.post-content h1,
.post-content h2,
.post-content h3,
.post-content h4 {
margin-top: 1.8em;
margin-bottom: 0.8em;
color: var(--green);
}
.post-content blockquote {
margin: 2em 0;
padding: 0.5em 1.5em;
border-left: 4px solid var(--green);
background-color: var(--entry);
font-style: italic;
color: var(--secondary);
}
.post-content code {
font-size: 16px;
background-color: var(--tertiary);
color: var(--primary);
border-radius: 4px;
}
.highlight {
padding: 0.6em;
}
.post-content pre code {
background-color: var(--entry) !important;
}
.post-content blockquote p {
margin: 0;
padding: 0.5em 0em;
}
/* *
* SEARCH PAGE FIXES
* */
#searchResults .post-entry {
border-radius: 0; /* Square borders like notes */
padding: 0;
border: 1px solid var(--tertiary);
margin-bottom: 8px;
}
#searchResults a {
position: relative;
display: flex !important;
justify-content: flex-start;
align-items: center;
padding: 12px 15px;
text-decoration: none !important;
color: var(--primary);
z-index: 1;
}
#searchResults a .search-meta {
margin-left: auto; /* Push date to the right */
font-size: 0.85rem;
color: var(--secondary);
}
#searchResults a:hover .search-title,
#searchResults a:hover .search-meta {
color: var(--theme) !important;
}
#searchResults a::before {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 0;
background-color: var(--green);
z-index: -1;
transition: height 0.3s cubic-bezier(0.19, 1, 0.22, 1);
}
#searchResults a:hover::before {
height: 100%;
}
#searchResults a:hover {
color: var(--theme) !important;
}

153
assets/js/fastsearch.js Normal file
View File

@@ -0,0 +1,153 @@
import * as params from '@params';
let fuse; // holds our search engine
let resList = document.getElementById('searchResults');
let sInput = document.getElementById('searchInput');
let first, last, current_elem = null
let resultsAvailable = false;
// load our search index
window.onload = function () {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
let data = JSON.parse(xhr.responseText);
if (data) {
// fuse.js options; check fuse.js website for details
let options = {
distance: 100,
threshold: 0.4,
ignoreLocation: true,
keys: [
'title',
'permalink',
'summary',
'content'
]
};
if (params.fuseOpts) {
options = {
isCaseSensitive: params.fuseOpts.iscasesensitive ?? false,
includeScore: params.fuseOpts.includescore ?? false,
includeMatches: params.fuseOpts.includematches ?? false,
minMatchCharLength: params.fuseOpts.minmatchcharlength ?? 1,
shouldSort: params.fuseOpts.shouldsort ?? true,
findAllMatches: params.fuseOpts.findallmatches ?? false,
keys: params.fuseOpts.keys ?? ['title', 'permalink', 'summary', 'content'],
location: params.fuseOpts.location ?? 0,
threshold: params.fuseOpts.threshold ?? 0.4,
distance: params.fuseOpts.distance ?? 100,
ignoreLocation: params.fuseOpts.ignorelocation ?? true
}
}
fuse = new Fuse(data, options); // build the index from the json file
}
} else {
console.log(xhr.responseText);
}
}
};
xhr.open('GET', "../index.json");
xhr.send();
}
function activeToggle(ae) {
document.querySelectorAll('.focus').forEach(function (element) {
// rm focus class
element.classList.remove("focus")
});
if (ae) {
ae.focus()
document.activeElement = current_elem = ae;
ae.parentElement.classList.add("focus")
} else {
document.activeElement.parentElement.classList.add("focus")
}
}
function reset() {
resultsAvailable = false;
resList.innerHTML = sInput.value = ''; // clear inputbox and searchResults
sInput.focus(); // shift focus to input box
}
// execute search as each character is typed
sInput.onkeyup = function (e) {
// run a search query (for "term") every time a letter is typed
// in the search box
if (fuse) {
let results;
if (params.fuseOpts) {
results = fuse.search(this.value.trim(), {limit: params.fuseOpts.limit}); // the actual query being run using fuse.js along with options
} else {
results = fuse.search(this.value.trim()); // the actual query being run using fuse.js
}
if (results.length !== 0) {
// build our html if result exists
let resultSet = ''; // our results bucket
for (let item in results) {
resultSet += `<li class="post-entry"><a href="${results[item].item.permalink}" aria-label="${results[item].item.title}">` +
`<div class="search-title"># ${results[item].item.title}</div>` +
`<div class="search-meta"><span class="delimiter">&bull;</span> ${results[item].item.date}</div></a></li>`
}
resList.innerHTML = resultSet;
resultsAvailable = true;
first = resList.firstChild;
last = resList.lastChild;
} else {
resultsAvailable = false;
resList.innerHTML = '';
}
}
}
sInput.addEventListener('search', function (e) {
// clicked on x
if (!this.value) reset()
})
// kb bindings
document.onkeydown = function (e) {
let key = e.key;
let ae = document.activeElement;
let inbox = document.getElementById("searchbox").contains(ae)
if (ae === sInput) {
let elements = document.getElementsByClassName('focus');
while (elements.length > 0) {
elements[0].classList.remove('focus');
}
} else if (current_elem) ae = current_elem;
if (key === "Escape") {
reset()
} else if (!resultsAvailable || !inbox) {
return
} else if (key === "ArrowDown") {
e.preventDefault();
if (ae == sInput) {
// if the currently focused element is the search input, focus the <a> of first <li>
activeToggle(resList.firstChild.lastChild);
} else if (ae.parentElement != last) {
// if the currently focused element's parent is last, do nothing
// otherwise select the next search result
activeToggle(ae.parentElement.nextSibling.lastChild);
}
} else if (key === "ArrowUp") {
e.preventDefault();
if (ae.parentElement == first) {
// if the currently focused element is first item, go to input box
activeToggle(sInput);
} else if (ae != sInput) {
// if the currently focused element is input box, do nothing
// otherwise select the previous search result
activeToggle(ae.parentElement.previousSibling.lastChild);
}
} else if (key === "ArrowRight") {
ae.click(); // click on active link
}
}

4
content/notes/_index.md Normal file
View File

@@ -0,0 +1,4 @@
---
title: "Notes"
description: "Quick notes, TILs, and short thoughts"
---

11
content/notes/demo.md Normal file
View File

@@ -0,0 +1,11 @@
---
title: "Sample Note"
date: 2025-08-20
# summary: "This is a sample note to demonstrate the notes section"
tags: ["example"]
draft: false
---
This is a sample note. Notes are meant to be short, quick thoughts or TILs (Today I Learned).
Delete this file and add your own notes here!

4
content/posts/_index.md Normal file
View File

@@ -0,0 +1,4 @@
---
title: "Posts"
description: "Long-form articles on software development, architecture, and engineering"
---

View File

@@ -1,19 +0,0 @@
---
author: ["Saurav Dhakal"]
title: "Understanding Separation of Concerns (SoC) in NestJS"
date: "2025-08-19"
summary: "A guide to understanding Separation of Concerns in NestJS using modules, services, and controllers."
tags: ["nestjs", "typescript", "architecture"]
categories: ["Backend Development", "NestJS"]
series: ["NestJS"]
ShowToc: true
TocOpen: false
---
When building applications, one of the most important design principles to keep in mind is **Separation of Concerns (SoC)**. NestJS, with its modular architecture, makes applying SoC almost effortless — but understanding _why_ it matters and _how_ to use it properly will help you write cleaner, testable, and future-proof code.
## What is Separation of Concerns and Why it Matters?
The basic idea is:
> A program should be divided into distinct sections, where each section addresses a single responsibility.

View File

@@ -0,0 +1,26 @@
---
title: "The Art of the Digital Garden"
date: 2026-04-01T08:55:00+05:45
draft: false
tags: ["philosophy", "gruvbox", "hugo"]
description: "Exploring the concept of a digital garden and how a refined Gruvbox theme enhances the experience."
---
Welcome to my digital garden. This is a place where I share my thoughts, projects, and lessons learned along the way. Unlike a traditional blog, a digital garden is always growing and evolving.
### Why Gruvbox?
Gruvbox is more than just a color scheme; it's a philosophy of contrast and harmony. By using the "Hard" contrast variant, we achieve a focus that is both easy on the eyes and aesthetically pleasing.
```javascript
const theme = "Gruvbox Dark Hard";
console.log(`Current theme: ${theme}`);
```
### The Beauty of Hugo
Hugo allows for lightning-fast build times and complete control over the structure of our content. Paired with a minimal theme like PaperMod, it provides the perfect canvas for technical writing.
> "A garden is never finished."
I hope you enjoy exploring these notes and posts as much as I enjoy writing them.

View File

@@ -1,8 +1,7 @@
---
title: "Search" # in any language you want
layout: "search" # necessary for search
# url: "/archive"
# description: "Description for Search"
summary: "search"
placeholder: "placeholder text in search input box"
title: "Search"
layout: "search"
url: "/search/"
summary: "Search posts and notes"
placeholder: "Search..."
---

View File

@@ -1,5 +1,5 @@
# DEmo
baseURL: https://example.org/
baseURL: "/"
relativeURLs: true
languageCode: en-us
title: Saurav Dhakal
theme: ["PaperMod"]
@@ -77,14 +77,12 @@ params:
# home-info mode
homeInfoParams:
Title: "Hi there \U0001F44B, I'm Saurav!"
Title: "Hi there \U0001F44B, Im Saurav!"
Content: >
Im a software engineer who enjoys building thoughtful systems and learning how things really work.
<br />
<br /><br />
This is my digital garden - notes, projects, and lessons along the way.
- <br />
<br /><br />
Checkout my [CV](files/CV.pdf) for my works and projects.
socialIcons:
@@ -97,7 +95,7 @@ params:
analytics:
google:
SiteVerificationTag: "G-V0CXG8ZEG2"
SiteVerificationTag: ""
cover:
hidden: true # hide everywhere but not in structured data
@@ -123,16 +121,27 @@ params:
menu:
main:
- identifier: posts
name: posts
name: Posts
url: /posts/
weight: 10
- identifier: tags
name: tags
url: /tags/
- identifier: notes
name: Notes
url: /notes/
weight: 20
- identifier: tags
name: Tags
url: /tags/
weight: 30
- identifier: search
name: Search
url: /search/
weight: 40
# Read: https://github.com/adityatelange/hugo-PaperMod/wiki/FAQs#using-hugos-syntax-highlighter-chroma
pygmentsUseClasses: true
markup:
goldmark:
renderer:
unsafe: true
highlight:
noClasses: false
# anchorLineNos: true
@@ -140,3 +149,4 @@ markup:
# guessSyntax: true
# lineNos: true
# style: monokai
googleAnalytics: "G-V0CXG8ZEG2"

View File

@@ -0,0 +1,7 @@
{{- $.Scratch.Add "index" slice -}}
{{- range site.RegularPages -}}
{{- if and (not .Params.searchHidden) (ne .Layout `archives`) (ne .Layout `search`) }}
{{- $.Scratch.Add "index" (dict "title" .Title "content" .Plain "permalink" .Permalink "summary" .Summary "date" (.Date.Format "Jan 2, 2006")) -}}
{{- end }}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}

19
layouts/index.html Normal file
View File

@@ -0,0 +1,19 @@
{{- define "main" }}
{{- if .Site.Params.homeInfoParams }}
<article class="first-entry home-info">
<header class="entry-header">
<h1>{{ .Site.Params.homeInfoParams.Title | markdownify }}</h1>
</header>
<div class="entry-content">
{{ .Site.Params.homeInfoParams.Content | markdownify }}
</div>
<footer class="entry-footer">
{{- partial "social_icons.html" (dict "align" site.Params.homeInfoParams.AlignSocialIconsTo) }}
</footer>
</article>
{{- end }}
{{- /* No recent posts/notes - clean home page */ -}}
{{- end }}{{/* end main */}}

22
layouts/notes/list.html Normal file
View File

@@ -0,0 +1,22 @@
{{- define "main" }}
<header class="page-header">
<h1>{{ .Title }}</h1>
{{- if .Description }}
<div class="post-description">{{ .Description }}</div>
{{- end }}
</header>
<ul class="notes-list">
{{- range .Pages }}
<li class="note-item">
<a href="{{ .Permalink }}">
<span class="note-title"># {{ .Title }}</span>
<span class="delimiter">&bull;</span>
<span class="note-date">{{ .Date.Format "Jan 2, 2006" }}</span>
</a>
</li>
{{- end }}
</ul>
{{- end }}{{/* end main */}}

View File

@@ -12,7 +12,7 @@
{{- end }}
{{- if (not site.Params.disableScrollToTop) }}
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
<a href="#top" aria-label="go to top" title="Go to Top" class="top-link" id="top-link" accesskey="g">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
<path d="M12 6H0l6-6z" />
</svg>

View File

@@ -0,0 +1,113 @@
{{ if .IsHome }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "{{- ( site.Params.schema.publisherType | default "Organization") | title -}}",
"name": {{ site.Title | jsonify }},
"url": {{ site.Home.Permalink | jsonify }},
"description": {{ site.Params.description | plainify | truncate 180 | jsonify }},
{{- if (eq site.Params.schema.publisherType "Person") }}
"image": {{ site.Params.assets.favicon | default "favicon.ico" | absURL | jsonify }},
{{- else }}
"logo": {{ site.Params.assets.favicon | default "favicon.ico" | absURL | jsonify }},
{{- end }}
"sameAs": [
{{- if site.Params.schema.sameAs }}
{{ range $i, $e := site.Params.schema.sameAs }}{{ if $i }}, {{ end }}{{ trim $e " " | jsonify }}{{ end }}
{{- else}}
{{ range $i, $e := site.Params.SocialIcons }}{{ if $i }}, {{ end }}{{ trim $e.url " " | jsonify }}{{ end }}
{{- end}}
]
}
</script>
{{- else if (or .IsPage .IsSection) }}
{{/* BreadcrumbList */}}
{{- $url := replace .Parent.Permalink ( printf "%s" site.Home.Permalink) "" }}
{{- $lang_url := strings.TrimPrefix ( printf "%s/" .Lang) $url }}
{{- $bc_list := (split $lang_url "/")}}
{{- $scratch := newScratch }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{{- range $index, $element := $bc_list }}
{{- $scratch.Add "path" (printf "%s/" $element ) | safeJS }}
{{- $bc_pg := site.GetPage ($scratch.Get "path") -}}
{{- if (and ($bc_pg) (gt (len . ) 0))}}
{{- if $index }}, {{end }}
{
"@type": "ListItem",
"position": {{ add 1 $index }},
"name": {{ $bc_pg.Name | jsonify }},
"item": {{ $bc_pg.Permalink | jsonify }}
}
{{- end }}
{{- end }}
{{- /* self-page addition */ -}}
, {
"@type": "ListItem",
"position": {{ add (len $bc_list) 1 }},
"name": {{ .Name | jsonify }},
"item": {{ .Permalink | jsonify }}
}
]
}
</script>
{{- if .IsPage }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": {{ .Title | plainify | jsonify }},
"name": {{ .Title | plainify | jsonify }},
"description": {{ with .Description | plainify }}{{ . | jsonify }}{{ else }}{{ .Summary | plainify | truncate 150 | jsonify }}{{ end }},
"keywords": [
{{- if .Params.keywords }}
{{ range $i, $e := .Params.keywords }}{{ if $i }}, {{ end }}{{ $e | jsonify }}{{ end }}
{{- else }}
{{ range $i, $e := .Params.tags }}{{ if $i }}, {{ end }}{{ $e | jsonify }}{{ end }}
{{- end }}
],
"wordCount": {{ .WordCount }},
"inLanguage": {{ .Language.Lang | default "en-us" | jsonify }},
"datePublished": {{ .PublishDate.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
"dateModified": {{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
{{- with (.Params.author | default site.Params.author) }}
"author":
{{- if (or (eq (printf "%T" .) "[]string") (eq (printf "%T" .) "[]interface {}")) -}}
[{{- range $i, $v := . -}}
{{- if $i }}, {{end -}}
{
"@type": "Person",
"name": {{ $v | jsonify }}
}
{{- end }}],
{{- else -}}
{
"@type": "Person",
"name": {{ . | jsonify }}
},
{{- end -}}
{{- end }}
"mainEntityOfPage": {
"@type": "WebPage",
"@id": {{ .Permalink | jsonify }}
},
"publisher": {
"@type": "{{- ( site.Params.schema.publisherType | default "Organization") | title -}}",
"name": {{ site.Title | jsonify }},
"logo": {
"@type": "ImageObject",
"url": {{ site.Params.assets.favicon | default "favicon.ico" | absURL | jsonify }}
}
}
}
</script>
{{- end }}{{/* .IsPage end */}}
{{- end -}}

97
layouts/partials/toc.html Normal file
View File

@@ -0,0 +1,97 @@
{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}}
{{- $has_headers := ge (len $headers) 1 -}}
{{- if $has_headers -}}
<div class="toc">
<details {{if (.Param "TocOpen") }} open{{ end }}>
<summary accesskey="c" title="">
<span class="details">{{- i18n "toc" | default "Table of Contents" }}</span>
</summary>
<div class="inner">
{{- if (.Param "UseHugoToc") }}
{{- .TableOfContents -}}
{{- else }}
{{- $largest := 6 -}}
{{- range $headers -}}
{{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
{{- $headerLevel := len (seq $headerLevel) -}}
{{- if lt $headerLevel $largest -}}
{{- $largest = $headerLevel -}}
{{- end -}}
{{- end -}}
{{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}
{{- $.Scratch.Set "bareul" slice -}}
<ul>
{{- range seq (sub $firstHeaderLevel $largest) -}}
<ul>
{{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
{{- end -}}
{{- range $i, $header := $headers -}}
{{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
{{- $headerLevel := len (seq $headerLevel) -}}
{{/* get id="xyz" */}}
{{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
{{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
{{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
{{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}
{{- if ne $i 0 -}}
{{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
{{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
{{- if gt $headerLevel $prevHeaderLevel -}}
{{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
<ul>
{{/* the first should not be recorded */}}
{{- if ne $prevHeaderLevel . -}}
{{- $.Scratch.Add "bareul" . -}}
{{- end -}}
{{- end -}}
{{- else -}}
</li>
{{- if lt $headerLevel $prevHeaderLevel -}}
{{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
{{- if in ($.Scratch.Get "bareul") . -}}
</ul>
{{/* manually do pop item */}}
{{- $tmp := $.Scratch.Get "bareul" -}}
{{- $.Scratch.Delete "bareul" -}}
{{- $.Scratch.Set "bareul" slice}}
{{- range seq (sub (len $tmp) 1) -}}
{{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
{{- end -}}
{{- else -}}
</ul>
</li>
{{- end -}}
{{- end -}}
{{- end -}}
{{- end }}
<li>
<a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify | safeHTML -}}">{{- $header | plainify | safeHTML -}}</a>
{{- else }}
<li>
<a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify | safeHTML -}}">{{- $header | plainify | safeHTML -}}</a>
{{- end -}}
{{- end -}}
<!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} -->
{{- $firstHeaderLevel := $largest }}
{{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
</li>
{{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
{{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
</ul>
{{- else }}
</ul>
</li>
{{- end -}}
{{- end }}
</ul>
{{- end }}
</div>
</details>
</div>
{{- end }}

25
layouts/posts/list.html Normal file
View File

@@ -0,0 +1,25 @@
{{- define "main" }}
<header class="page-header">
<h1>{{ .Title }}</h1>
{{- if .Description }}
<div class="post-description">{{ .Description }}</div>
{{- end }}
</header>
<ul class="posts-list">
{{- range .Pages }}
<li class="post-item">
<a href="{{ .Permalink }}">
<span class="post-title"># {{ .Title }}</span>
<span class="delimiter">&bull;</span>
<span class="post-date">{{ .Date.Format "Jan 2, 2006" }}</span>
</a>
{{- if .Summary }}
<p class="post-summary">{{ .Summary | plainify | truncate 150 }}</p>
{{- end }}
</li>
{{- end }}
</ul>
{{- end }}{{/* end main */}}

View File

@@ -1,2 +0,0 @@
<!doctype html><html lang=en dir=auto data-theme=dark><head><script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;path=livereload" data-no-instant defer></script><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name=robots content="index, follow"><title>404 Page not found | Saurav Dhakal</title><meta name=keywords content><meta name=description content="A personal blog"><meta name=author content="saurav"><link rel=canonical href=http://localhost:1313/404.html><meta name=google-site-verification content="G-V0CXG8ZEG2"><link crossorigin=anonymous href=/assets/css/stylesheet.1819d1b52fd9f2af4d88316fde3f9b918c48d08dac9a2e1ef0a7ca49d1f18ddb.css integrity="sha256-GBnRtS/Z8q9NiDFv3j+bkYxI0I2smi4e8KfKSdHxjds=" rel="preload stylesheet" as=style><link rel=icon href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><link rel=icon type=image/png sizes=16x16 href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><link rel=icon type=image/png sizes=32x32 href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><link rel=apple-touch-icon href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><link rel=mask-icon href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><meta name=theme-color content="#2e2e33"><meta name=msapplication-TileColor content="#2e2e33"><link rel=alternate hreflang=en href=http://localhost:1313/404.html><noscript><style>#theme-toggle,.top-link{display:none}</style></noscript><meta property="og:url" content="http://localhost:1313/404.html"><meta property="og:site_name" content="Saurav Dhakal"><meta property="og:title" content="404 Page not found"><meta property="og:description" content="A personal blog"><meta property="og:locale" content="en-us"><meta property="og:type" content="website"><meta name=twitter:card content="summary"><meta name=twitter:title content="404 Page not found"><meta name=twitter:description content="A personal blog"></head><body class=list id=top><header class=header><nav class=nav><div class=logo><a href=http://localhost:1313/ accesskey=h title="SauravDhakal (Alt + H)">SauravDhakal</a><div class=logo-switches></div></div><ul id=menu><li><a href=http://localhost:1313/posts/ title=posts><span>posts</span></a></li><li><a href=http://localhost:1313/tags/ title=tags><span>tags</span></a></li></ul></nav></header><main class=main><div class=not-found>404</div></main><footer class=footer><span>Copyright &copy; 2025 SauravDhakal</span></footer><a href=#top aria-label="go to top" title="Go to Top (Alt + G)" class=top-link id=top-link accesskey=g><svg viewBox="0 0 12 6" fill="currentColor"><path d="M12 6H0l6-6z"/></svg>
</a><script>let menu=document.getElementById("menu");if(menu){const e=localStorage.getItem("menu-scroll-position");e&&(menu.scrollLeft=parseInt(e,10)),menu.onscroll=function(){localStorage.setItem("menu-scroll-position",menu.scrollLeft)}}document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){e.preventDefault();var t=this.getAttribute("href").substr(1);window.matchMedia("(prefers-reduced-motion: reduce)").matches?document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView():document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView({behavior:"smooth"}),t==="top"?history.replaceState(null,null," "):history.pushState(null,null,`#${t}`)})})</script><script>var mybutton=document.getElementById("top-link");window.onscroll=function(){document.body.scrollTop>800||document.documentElement.scrollTop>800?(mybutton.style.visibility="visible",mybutton.style.opacity="1"):(mybutton.style.visibility="hidden",mybutton.style.opacity="0")}</script></body></html>

Some files were not shown because too many files have changed in this diff Show More