Markdown formatting

Basic formatting Syntax

Paragraphs

If you want to prevent spaces from collapsing or add multiple blank spaces, you can use the &nbsp; (non-breaking space) or <br> (line break) HTML tags.

Footnotes

You can add footnotes1 to your notes using the following syntax:

This is a simple footnote[^1].
 
[^1]: This is the referenced text.
[^2]: Add 2 spaces at the start of each new line.
  This lets you write footnotes that span multiple lines.
[^note]: Named footnotes still appear as numbers, but can make it easier to identify and link references.

You can also inline footnotes in a sentence. Note that the caret goes outside the brackets.

You can also use inline footnotes. ^[This is an inline footnote.]

You can also use inline footnotes. ^[This is an inline footnote.]

Note

Inline footnotes only work in reading view, not in Live Preview.

Comments

You can add comments by wrapping text with % by using it twice. Comments are only visible in Editing view.

Testing out a … (inline comment), which is hidden.

This is an  (inline) comment where the word inline is enclosed with % twice.
 
Below is a block comment, which is hidden in view mode.
 

Rendering images

Custom image size with caption and background color

<div style="text-align: center;">
 
<img src="Llama-pre-training-data-details.png" alt="Llama pre-training data details" style="max-width: 400px; height: auto; background-color: white; display: block; margin: 0 auto;">
 
<p style="font-size: 0.9em; color: #666; margin: 4px 0 0 0; font-style: italic;">Figure: Llama pre-training data details showing the composition and sources of training data used in the model</p>
 
</div>
Llama pre-training data details

Figure: Llama pre-training data details showing the composition and sources of training data used in the model

Obsidian Flavored Markdown

Callouts

Supported callout types

  • > [!note]
  • > [!abstract]
    • Aliases: summarytldr
  • > [!info]
  • > [!todo]
  • > [!tip]
    • Aliases: hint, important
  • > [!success]
    • Aliases: check, done
  • > [!question]
    • Aliases: help, faq
  • > [!warning]
    • Aliases: caution, attention
  • > [!failure]
    • Aliases: fail, missing
  • > [!danger]
    • Aliases: error
  • > [!bug]
  • > [!example]
  • > [!quote]
    • Aliases: cite

Nested callouts

> [!question] Can callouts be nested?
> 
> > [!todo] Yes!, they can.
> > > [!example]  You can even use multiple layers of nesting.

Foldable callouts

 > [!note]- Foldable
 > This is a foldable callout

Plugins


Extending Obsidian

CSS Snippets

table-wrap.css

/* 
	Cribbed from the excellent ITS Theme
	https://github.com/SlRvb/Obsidian--ITS-Theme 
*/
.cm-s-obsidian .HyperMD-table-row.HyperMD-table-row.HyperMD-table-row {
	white-space: pre-wrap;
	min-width: min-content;
	}

frozen-headers.css

/* Freeze table headers in Obsidian */
table {
    border-collapse: collapse;
    width: 100%;
}
 
thead {
    position: sticky;
    top: 0;
    background-color: var(--background-primary);
    z-index: 2; /* Ensure it stays above the content */
}
 
th {
    background-color: var(--background-secondary);
    color: var(--text-normal);
    text-align: left;
    padding: 8px;
}
 
td {
    padding: 8px;
    border: 1px solid var(--background-modifier-border);
}

mermaid-tweak.css

/*
Source(s): 
    - https://stackoverflow.com/questions/78935770/how-to-set-rendered-mermaid-diagrams-width-to-be-based-on-screen-size-in-obsidia
    - https://forum.obsidian.md/t/resize-and-align-mermaid-diagrams/7019
    - https://www.reddit.com/r/ObsidianMD/comments/1crsgop/til_about_mermaid_diagrams/?rdt=50293
Date updated: 2024-02-14 (YYYY-MM-DD)
*/
 
/** Set Mermaid Diagrams to 100% width of screen by default */
 
.mermaid svg {
    display: block;
    width: 100%;
    margin: 0;
    padding: 0;
}
 
/** On hover, make the diagram full width and enable horizontal scrolling */
 
div:has(> .mermaid):hover {
    width: auto !important;
}
 
.mermaid:hover {
    overflow: scroll;
    padding: 0;
    margin: 0;
    text-align: left;
}
 
.mermaid:hover svg {
    display: block;
    width: auto;
    margin: 0;
    padding: 0;
}

customer.scss for Animated GIFs

@use "./base.scss";
 
// put your custom CSS here!
 
/* Animated emoji styles */
.animated-emoji {
  display: inline-block;
  vertical-align: middle;
  margin: 0;
  padding: 0;
  
  img {
    display: inline-block;
    vertical-align: middle;
    margin: 0;
    padding: 0;
  }
}

Example: Without custom css

  • ✅ Example: With custom css class in quartz styles
  • ✅

Env setup

Quartz requires at least Node  and npm to function correct. For more details, refer Get Started

Github repo setup

# Clone the repo locally
git clone https://github.com/prasanth-ntu/prasanth.io
 
# list all the repositories that are tracked
git remote -v
 
# if you don't have upstream as a remote, add it so updates work
git remote add upstream https://github.com/jackyzha0/quartz.git

Updating the Site

Key commands

  • Building the quartz
    • This will start a local web server to run our Quartz on our computer. We can view it at http://localhost:8080/
npx quartz build --serve

What actually happens during npx quartz build --serve

  1. Prebuild runs first (sync-slideshow.sh + sync-html.sh) — copies slideshow and HTML files into quartz/static/
  2. esbuild transpiles the Quartz config, plugins, and components into a single cached JS file (quartz/.quartz-cache/transpiled-build.mjs)
  3. buildQuartz() runs — parses all markdown from content/, filters out drafts, then emits output files to public/ using these emitter plugins:
EmitterWhat it produces
AliasRedirectsRedirect pages for aliases defined in frontmatter
ComponentResourcesJS/CSS bundles for interactive components
ContentPageThe actual HTML page for each markdown file
FolderPageIndex pages for folders
TagPagePages for each tag
ContentIndexRSS feed + sitemap
AssetsProcessed SCSS → CSS
StaticCopies quartz/static/ to public/static/
NotFoundPageThe 404 page
CustomOgImagesOpen Graph preview images (.webp) for every page — the thumbnail shown when sharing links on social media, Slack, Discord, etc. This is the slowest emitter as it renders text onto images using satori + sharp for each page
  1. Since --serve is passed, it also:
    • Sets --watch to true — auto-rebuilds when source files change
    • Starts an HTTP server on localhost:8080 serving files from public/
    • Starts a WebSocket server for live reload (browser refreshes automatically on rebuild)
    • Handles URL routing (e.g. /foo//foo/index.html, /foo/foo.html)

With vs without --serve

  • npx quartz build: Builds the site to public/ and exits. This is what GitHub Actions uses.
  • npx quartz build --serve: Builds, starts a local server with live reload, and watches for changes. This is for local development.
  • Sync the change to Github & Deploy
    • Run this command every time we want to push updates to our repository.
npx quartz sync

What actually happens during npx quartz sync

The sync command is not a build — it’s a git orchestration tool. The actual build happens on GitHub Actions after the push.

Local: npm run sync

The sync script in package.json is just npx quartz sync. npm automatically runs the prebuild script (bash sync-slideshow.sh && bash sync-html.sh) before any build/sync/serve script since it follows the pre<script> naming convention.

1. Prebuild (auto-triggered: bash sync-slideshow.sh && bash sync-html.sh)

  • Generates Paige AI slideshow HTML files → quartz/static/paige-slides/
  • Copies architecture HTML files (Spark, Docker, K8s, etc.) → quartz/static/pages/

2. npx quartz sync (defined in quartz/cli/handlers.js)

  1. Backs up content/ folder to .quartz-cache/content-cache
  2. Commits all changes: git add . && git commit -m "Quartz sync: <timestamp>"
  3. Pulls from origin v4 (uses --autostash -X ours to keep local changes on conflicts)
  4. Restores content from cache
  5. Pushes to origin v4

GitHub Actions: Build & Deploy

The push to v4 triggers .github/workflows/deploy.yaml:

  1. npm ci — clean install dependencies
  2. npm run build — npm auto-runs prebuild first, then npx quartz build:
    • Prebuild: copies slideshow + HTML files into quartz/static/
    • Parses all .md files from content/
    • Filters out drafts
    • Emits HTML/assets to public/
  3. Uploads public/ as a GitHub Pages artifact
  4. Deploys to GitHub Pages → https://prasanth.io/

End-to-End Flow

Local                                GitHub Actions
─────                                ──────────────
npm run sync
  ├─ prebuild (slideshow + html)
  └─ npx quartz sync
       ├─ git commit
       ├─ git pull origin v4
       └─ git push origin v4 ──────→ deploy.yaml triggered
                                       ├─ npm ci
                                       ├─ npm run build
                                       │    ├─ prebuild (auto-triggered)
                                       │    └─ npx quartz build
                                       │         ├─ parse .md files
                                       │         ├─ filter drafts
                                       │         └─ emit → public/
                                       ├─ upload artifact
                                       └─ deploy to GitHub Pages
                                            └─ prasanth.io ✅

How prebuild works

The prebuild script in package.json uses npm’s pre<script> convention — npm automatically runs it before build, sync, or serve. This means:

  • Locally: Prebuild runs before serve/sync so slideshow and HTML pages render correctly during development
  • On CI: Prebuild runs before build because the GitHub runner starts from a fresh git clone and needs to generate the static files from source

Previously, build/serve/sync scripts also had an explicit npm run prebuild && prefix, causing prebuild to run twice. This was fixed by removing the redundant explicit calls.

Custom fix: Skip slugification for /static/ paths

Quartz’s link transformer (quartz/plugins/transformers/links.ts) processes all internal <a href> and <iframe src> URLs through transformLink()slugifyFilePath(), which converts spaces to hyphens and strips .html extensions. This is correct for content links (e.g. [[My Note]]My-Note), but breaks paths pointing to actual static files.

The problem: An iframe like <iframe src="/static/pages/Spark%20Architecture.html"> gets slugified to /static/pages/Spark-Architecture (no extension, hyphens instead of spaces), causing 404s since the real file on disk is Spark Architecture.html.

The fix: Added guards to skip transformLink() for any path starting with /static/:

// For <a href> tags (line ~102):
const isInternal = !(
  isAbsoluteUrl(dest, { httpOnly: false }) ||
  dest.startsWith("#") ||
  dest.startsWith("/static/")  // ← added
)
 
// For <iframe src>, <img src>, etc. (line ~150):
if (
  !isAbsoluteUrl(node.properties.src, { httpOnly: false }) &&
  !node.properties.src.startsWith("/static/")  // ← added
) {

Why this is safe: /static/ contains files served as-is (HTML pages, images, fonts) — not Quartz content. Skipping slugification means these paths won’t appear in the link graph or get popover previews, which is correct since they’re not content pages.

  • Upgrading Quartz
    • To fetch the latest Quartz updates, simply run
npx quartz update

For more details, refer Quartz 4.0 official documentation


Customising the site

ContentMeta.tsx

interface ContentMetaOptions {
  ...
  showAuthor: boolean
}

const defaultOptions: ContentMetaOptions = {
  ...
  showAuthor: true,
}

export default ((opts?: Partial<ContentMetaOptions>) => {
  ...
  
  function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) {
      ...
      
      // Display author if enabled and available
      if (options.showAuthor && fileData.frontmatter?.author) {
        segments.push(<span>Book by {fileData.frontmatter.author}</span>)
      }

Obsidian References

Site Inspirations

Tutorials & Youtube Videos

Documentations

  • Quartz 4.0
    • The documentation I referred to publish my Obsidian notes for free with Quarts

Non-obsidian Site Inspirations

Footnotes

  1. This is the referenced text.