Source code for pharzan.com, Farzan Tinati's personal website and blog.
The site is built with Lume and Deno and deployed to Firebase Hosting.
- Deno
- Node.js 20 or newer and pnpm for audio generation
- Firebase CLI for deployment
- Google Cloud CLI for Vertex AI authentication
- ImageMagick on Linux, or
sipson macOS, for image optimization and deployment - ffmpeg for audio generation
Clone the repository and start the local development server:
git clone https://github.com/pharzan/pharzan.com.git
cd pharzan.com
deno task serveLume will print the local URL in the terminal and rebuild the site when source files change.
| Command | Description |
|---|---|
deno task serve |
Start the local development server |
deno task build |
Generate the production site in _site/ |
deno task prepare |
Optimize images and generate the production site |
deno task cms |
Start the Lume CMS |
deno task new-post <filename> <title> <description> |
Create a post in posts/ |
deno task optimize-images |
Resize and optimize images in assets/ |
deno task setup-hooks |
Configure the repository's Git hooks |
deno task deploy |
Optimize images, build, and deploy to Firebase Hosting |
pnpm generate-audio posts/<slug>.md |
Generate and cache an MP3 narration for one post |
pnpm translate-post posts/<slug>.md <language> |
Translate one post with Gemini |
The same commands are available through make:
make help
make serve
make build
make prepare
make deployFor example, to create a post:
deno task new-post hello-world "Hello, world" "A short description of the post"
# Or with Make
make new-post SLUG=hello-world TITLE="Hello, world" DESCRIPTION="A short description of the post".
├── _config.ts # Lume configuration and plugins
├── _includes/ # Shared page layouts
├── assets/ # Styles, images, icons, and downloadable files
├── posts/ # Blog posts written in Markdown
├── scripts/ # Content and asset utility scripts
├── index.vto # Home page
├── me.md # About page
└── firebase.json # Firebase Hosting configuration
Posts live in posts/ and use YAML front matter. A typical post starts with:
---
layout: layout.vto
title: Post title
description: "A concise description for search engines and link previews"
bodyClass: me-page
date: 2026-01-01 12:00
tags: [example]
---Use the new-post task to generate this structure automatically. Add post images under assets/images/ and run deno task optimize-images before committing large image files.
Install the Node dependencies, select a billed Google Cloud project with the Vertex AI API enabled, and create Application Default Credentials (ADC):
pnpm install
gcloud config set project PROJECT_ID
gcloud services enable aiplatform.googleapis.com
gcloud auth application-default login
pnpm generate-audio posts/example.mdThe command uses ADC to authenticate to Vertex AI. It reads the project from GOOGLE_CLOUD_PROJECT when set, otherwise it uses the active gcloud project; GOOGLE_CLOUD_LOCATION is optional and defaults to global.
It extracts the human-readable parts of the post, splits long articles at prose boundaries, generates narration with gemini-2.5-pro-tts, and writes assets/audio/example.mp3. It excludes front matter, code blocks, images, HTML, and raw URLs. The embedded narration direction uses the Orus voice with an engaging, persuasive, low-pitch delivery.
Generated MP3 files and their .audio-cache/<slug>.json history are intended to be committed. The history stores every generation attempt, token usage, and its estimated USD cost. Temporary resumable chunks under .audio-cache/chunks/ are ignored by Git. Unchanged content is skipped; regenerate it explicitly with:
pnpm generate-audio posts/example.md --forceCost figures are estimates based on the pricing constants in scripts/generate-audio.ts; Cloud Billing remains the authoritative source. The command requires ffmpeg and outputs a mono, 24 kHz, 64 kbps MP3.
Translated posts can be narrated in the same way:
pnpm generate-audio posts/example.fa.mdThis creates assets/audio/example.fa.mp3. The player appears automatically on /posts/example/fa/ when that file exists. Language availability and launch readiness depend on the Gemini TTS model; some languages, including Persian, are currently Preview.
All posts use a natural conversational delivery instead of an audiobook-style narration. English, Persian, and Turkish have tailored prompts for contemporary spoken cadence and pronunciation; other languages use the same language-neutral conversational direction. Regenerate an existing MP3 after changing the source or delivery prompt:
pnpm generate-audio posts/example.fa.md --forceTranslation uses the same Google Cloud project, Vertex AI API, and Application Default Credentials as audio generation. Pass a BCP 47 language code such as nb, fr, or pt-BR:
pnpm translate-post posts/example.md nbThe command translates the title, description, and human-readable Markdown while asking Gemini to preserve code, URLs, link destinations, image paths, HTML attributes, and Markdown structure. It appends a sentence in the target language identifying the model used for translation. It creates posts/example.nb.md, served at /posts/example/nb/. When one or more translations exist, the article displays language badges linking between the English original and every translation.
Existing translation files are not overwritten unless explicitly requested:
pnpm translate-post posts/example.md nb --forceSet GEMINI_TRANSLATION_MODEL to override the default gemini-2.5-flash model.
Authenticate the Firebase CLI, then run:
firebase login
deno task deployThe deploy task optimizes images, builds _site/, and publishes the default Firebase Hosting site. The GitHub Actions deployment uses the same preparation task.