automatic-fortnight

SS# Saugat Dhakal — Portfolio (Python / Flask)

A premium, editorial, dark-first personal portfolio built entirely in Python (Flask + Jinja2), with vanilla CSS/JS handling the motion and interaction design — no Node.js, React, or build step required.


1. Folder structure

portfolio/
├── app.py                  # Flask app: all routes + SEO (sitemap/robots)
├── requirements.txt
├── data/
│   └── content.py          # CMS-style content: projects, skills, blog, etc.
├── templates/
│   ├── base.html            # Shared layout: nav, footer, theme, cursor
│   ├── home.html
│   ├── about.html
│   ├── work.html             # Project archive (filters + search)
│   ├── project_detail.html   # Case-study template
│   ├── skills.html
│   ├── experience.html
│   ├── services.html
│   ├── process.html
│   ├── blog.html
│   ├── blog_article.html
│   ├── contact.html
│   ├── resume.html
│   └── 404.html
└── static/
    ├── css/style.css        # Full design system (colors, type, motion)
    ├── js/main.js            # Theme toggle, cursor, reveal, filters, nav
    └── images/favicon.svg    # SD monogram favicon

2. Installation

cd portfolio
python3 -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install -r requirements.txt

3. Development command

flask --app app run --debug

Open http://127.0.0.1:5000. The dev server auto-reloads on file changes.

4. “Build” / production command

Flask apps don’t have a static build step — for production, run behind a WSGI server:

pip install gunicorn
gunicorn app:app -w 4 -b 0.0.0.0:8000

5. Environment variables

None are required to run the site as-is. If you wire up real email sending on the contact form (see app.pycontact()), you’ll likely add something like:

SMTP_HOST=...
SMTP_USER=...
SMTP_PASSWORD=...
CONTACT_RECEIVER_EMAIL=...

Load them with python-dotenv or your platform’s env-var settings — never hardcode secrets into app.py or data/content.py.

6. Explanation of major components

7. Adding a project

Open data/content.py and add a new dict to the PROJECTS list:

{
    "slug": "your-project-slug",       # used in the URL /work/<slug>
    "number": "05",
    "title": "Project Name",
    "category": "Full-stack Development",
    "categories": ["development"],      # used by the Work page filters
    "year": "2026",
    "technology": ["Python", "React"],
    "description": "One or two sentence summary.",
    "role": "...", "tools": "...", "team": "...", "status": "...",
    "problem": "...", "research": "...", "strategy": "...",
    "design_notes": "...", "development_notes": "...",
    "challenges": "...", "solution": "...",
    "results": "...", "lessons": "...",
},

It will automatically appear on the homepage (first 4), the Work archive, and get its own case-study page at /work/your-project-slug.

8. Adding a blog post

Add a new dict to BLOG_POSTS in data/content.py:

{
    "slug": "your-post-slug",
    "title": "Post Title",
    "category": "Development",          # must match a BLOG_CATEGORIES entry
    "tags": ["Development", "Python"],
    "excerpt": "One-sentence summary shown on cards.",
    "date": "August 2026",
    "reading_time": "5 min read",
    "author": "Saugat Dhakal",
    "body": ["Paragraph one.", "Paragraph two."],
},

It appears on /blog and at /blog/your-post-slug automatically.

9. Changing personal information

Everything — name, role, tagline, email, social links, skills, experience, services, process steps — lives in data/content.py. Edit the PROFILE, SOCIAL_LINKS, SKILLS, EXPERIENCE, EDUCATION, and SERVICES structures at the top of the file. Anything marked # PLACEHOLDER is intentionally fake/unset — replace it with real information before publishing (no fabricated stats, clients, or testimonials were added).

To change the accent color or theme values, edit the CSS custom properties at the top of static/css/style.css (:root for dark, html[data-theme="light"] for light).

10. Deployment

Any host that runs a Python WSGI app works:

Before going live: replace every # PLACEHOLDER value in data/content.py, set a real contact-form email backend, and point SOCIAL_LINKS URLs at real profiles.


Notes on scope: the contact form currently validates and acknowledges submissions but does not send email — wire up an SMTP or transactional-email provider in app.pycontact() before relying on it. No employment history, clients, awards, statistics, or testimonials were invented; anything unknown is a labeled placeholder in data/content.py, ready for you to fill in. # Portfolio-website-m # automatic-fortnight