Skip to content

MkDocs Setup

README.md#

I keep my Github repo private and deploy via Cloudflare Pages & Cloudflare Worker CI.

This article will basically get you up to speed for how I build my site.

README.md

MkDocs 1080p Fix Theme#

This project uses my custom mkdocs-material theme fork.

It changes a few lines to fix the rendering of pages around ~50% of a 1080p display.

Download#

First, we need to clone both this repo and my custom theme.

git clone --recursive <REPO>

Or for SSH (if HTTPS isn't working):

[email protected]:<USERNAME>/<REPO>.git

If you just cloned the main repo, navigate to the theme directory and init the submodule:

WARN: This may delete uncommitted changes, commit and push what you have first.

cd theme/mkdocs-material-1080p-fix/
git submodule update --init --recursive

Build & Serve#

Built on Python 3.14

- optimize plugin uses pngquant, which is a PATH item. CI doesn't like it. CI can use pngquant-cli but untrusted.

NOTE: I have - optimize disabled, preferring to manually do it until a better option presents itself.

- privacy plugin requires symlinking, which requires Windows developer mode enabled or symlink policy enabled.

NOTE: You could also remove the - privacy plugin, or enabling on deploy.

As admin:

Set-ExecutionPolicy Bypass

Install requirements (dev):

python -m venv venv
./venv/Scripts/activate # Linux: . venv/bin/activate
pip install -r requirements.txt

Serve:

mkdocs serve --livereload

CI#

Deployed via Cloudflare worker upon push to Github.

Default mkdocs profile (mkdocs build and deploying /site) using Python 3.13 (Seems not to matter).

https://developers.cloudflare.com/pages/configuration/build-image/#languages-and-runtime


Mkdocs.yaml#

Here is my mkdocs.yaml, the main configuration section for the site.

I leave things commented out that I may use in the future.

My Warning
/mkdocs.yaml
site_name: "ComfyTechTips"
site_url: https://comfytechtips.org
site_description: "ComfyTechTips - Making Technology Comfortable"
site_author: "ComfyTechTips"
copyright: "<a href='https://creativecommons.org/licenses/by/4.0/'>CC BY 4.0</a><br>Copyright &copy; 2025 ComfyTechTips"

theme:
name: material
language: en
logo: assets/ctt-invis.png
favicon: assets/favicon/favicon.ico
icon:
    repo: octicons/code-24
    edit: material/pencil-box
    view: material/code-not-equal-variant

font:
    text: Source Sans Pro
    code: Google Sans Code

features:
    - announce.dismiss
    - content.code.copy
    - content.tabs.link
    - content.code.annotate
    # - header.autohide
    - navigation.expand
    - navigation.footer
    - navigation.instant
    - navigation.instant.progress
    - navigation.instant.prefetch
    - navigation.path
    - navigation.tabs
    - navigation.tabs.sticky
    # - navigation.top
    - navigation.sections
    - tabs
    - toc.integrate
    - toc.follow

palette:
    # Palette toggle for light mode
    - media: "(prefers-color-scheme: light)"
    scheme: default
    primary: teal
    accent: green
    toggle:
        icon: material/weather-night
        name: Switch to Dark Mode

    # Palette toggle for dark mode
    - media: "(prefers-color-scheme: dark)"
    scheme: slate
    primary: black
    accent: teal
    toggle:
        icon: material/weather-sunny
        name: Switch to Light Mode

markdown_extensions:
- admonition
- attr_list
- codehilite:
    guess_lang: false
    linenums: false
- def_list
- footnotes
- md_in_html
- meta
- pymdownx.arithmatex
- pymdownx.betterem:
    smart_enable: all
- pymdownx.caret
- pymdownx.critic
- pymdownx.details
- pymdownx.emoji:
    emoji_index: !!python/name:material.extensions.emoji.twemoji
    emoji_generator: !!python/name:material.extensions.emoji.to_svg
- pymdownx.inlinehilite
- pymdownx.magiclink
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.superfences
- pymdownx.tabbed:
    alternate_style: true
- pymdownx.tasklist
- pymdownx.tilde
- sane_lists
- toc:
    toc_depth: "3"
    permalink: '#'
    slugify: !!python/object/apply:pymdownx.slugs.slugify
        kwds:
        case: lower

plugins:
- awesome-nav:
    filename: .nav.yml
    logs:
        nav_override: warning
        root_title: warning
        root_hide: warning
        no_matches: warning
- blog:
    blog_dir: .
    blog_toc: true
    post_dir: blog
    post_url_format: "blog/{slug}"
    # archive: false
    archive_name: "Blog"
    archive_toc: true
    categories: false
- exclude:
    glob:
        - "*.tmp"
        - "*.gz"
    regex:
        - '.*\.(tmp|bin|tar)$'
- git-revision-date-localized:
    timezone: America/Chicago
    exclude:
        - index.md
    # If git logs can't be read (for generated/out-of-repo files), fall back to the build date
    fallback_to_build_date: true
- glightbox
- minify:
    minify_html: true
    htmlmin_opts:
        remove_comments: true
    cache_safe: true
    minify_css: true
    css_files:
        - stylesheets/extra.css
    minify_js: true
# - privacy # Breaks glightbox on first load as of 2025-11
# - optimize # requires pngquant PATH, CI doesn't like. OR pngquant-cli, but untrusted. OR pre-optimize before deploy manually.
- redirects:
    # redirect_maps:
        # 'old.md': 'databases/concepts/Databases.md'
# - rss:
#     match_path: "blog.*" # Ensure site_url is set, else won't work in prod
#     pretty_print: true
#     use_git: false
#     date_from_meta:
#       as_creation: date.created
- search
- autolinks
# - unused_files: Test
#       dir: attachment
#       excluded_files: .*-github-1.png

extra_css:
- stylesheets/extra.css

extra_javascript:
- javascripts/extra.js

extra:
generator: false
social:
    # - icon: fontawesome/solid/envelope
    #   link: mailto:[email protected]
    - icon: fontawesome/brands/github
    link: https://github.com/comfytechtips
    # - icon: simple/rss
    #   link: /feed_rss_created.xml

Extra.js#

Here is my custom javascript for the site, just the arrow on the homepage:

Extra.js
/docs/javascripts/extra.js
// All this is my vibecoded arrow to direct first time visitors, don't judge!

// Make site title clickable and return to home
document.addEventListener('DOMContentLoaded', function() {
  const siteTitle = document.querySelector('.md-header__title');
  if (siteTitle) {
    siteTitle.addEventListener('click', function() {
      window.location.href = '/';
    });
  }
});

// Dynamic arrow from #recommend-text to search box (only on index/home page, first visit only)
function drawDynamicArrow() {
  // Check if arrow has been dismissed
  if (sessionStorage.getItem('arrowDismissed') === 'true') {
    return;
  }

  // Only draw arrow on the home/index page
  const recommendText = document.getElementById('recommend-text');
  const searchBox = document.querySelector('.md-search');

  // Remove existing arrow if present
  const existingArrow = document.getElementById('dynamic-arrow-svg');

  // If not on index page or elements don't exist, remove arrow and exit
  if (!recommendText || !searchBox) {
    if (existingArrow) existingArrow.remove();
    return;
  }

  if (existingArrow) existingArrow.remove();

  // Get positions
  const textRect = recommendText.getBoundingClientRect();

  // Check if recommend text is covered by header
  const mdHeader = document.querySelector('.md-header');
  if (mdHeader) {
    const headerRect = mdHeader.getBoundingClientRect();
    // If header is visible and the text's top is above header bottom
    if (headerRect.bottom > 0 && textRect.top < headerRect.bottom) {
      // Text is covered/overlapped by header, hide arrow
      if (existingArrow) existingArrow.remove();
      return;
    }
  }

  // Check if recommend text is covered by tabs
  const mdTabs = document.querySelector('.md-tabs');
  if (mdTabs) {
    const tabsRect = mdTabs.getBoundingClientRect();
    // If tabs are below the top of the page and the text's top-right corner is below tabs bottom
    if (tabsRect.bottom > 0 && textRect.top < tabsRect.bottom && textRect.right > tabsRect.left) {
      // Text is covered/overlapped by tabs, hide arrow
      if (existingArrow) existingArrow.remove();
      return;
    }
  }

  // Check both search overlay and search box
  const searchOverlay = document.querySelector('.md-search__overlay');
  const searchBoxRect = searchBox.getBoundingClientRect();
  const overlayRect = searchOverlay ? searchOverlay.getBoundingClientRect() : null;

  let targetElement;
  let searchRect;

  // Determine which element is visible and usable
  if (overlayRect && overlayRect.width > 0 && overlayRect.height > 0 &&
      overlayRect.left >= 0 && overlayRect.right <= window.innerWidth) {
    // Overlay is visible and in viewport - use it
    targetElement = searchOverlay;
    searchRect = overlayRect;
  } else if (searchBoxRect.width > 0 && searchBoxRect.height > 0 &&
             searchBoxRect.left >= 0 && searchBoxRect.right <= window.innerWidth) {
    // Search box is visible and in viewport - use it
    targetElement = searchBox;
    searchRect = searchBoxRect;
  } else {
    // Default to whichever element exists
    targetElement = searchBox;
    searchRect = searchBoxRect;
  }

  // Calculate start and end points
  // Start at top-right of recommend text
  const startX = textRect.right;
  const startY = textRect.top;
  // End at bottom-left of search element (overlay on small screens, box on larger screens)
  const endX = searchRect.left;
  const endY = searchRect.bottom;

  // Calculate control points for curved path
  const midX = startX + (endX - startX) * 0.5;
  const midY = startY - Math.abs(endY - startY) * 0.5;

  // Create SVG with defs for marker
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  svg.id = 'dynamic-arrow-svg';
  svg.setAttribute('width', '100%');
  svg.setAttribute('height', '100%');

  // Create defs for arrowhead marker
  const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  const marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
  marker.setAttribute('id', 'arrowhead-dynamic');
  marker.setAttribute('markerWidth', '10');
  marker.setAttribute('markerHeight', '10');
  marker.setAttribute('refX', '9');
  marker.setAttribute('refY', '3');
  marker.setAttribute('orient', 'auto');
  marker.setAttribute('markerUnits', 'strokeWidth');

  const arrowShape = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
  arrowShape.setAttribute('points', '0 0, 10 3, 0 6');
  arrowShape.setAttribute('fill', '#ff6600');

  marker.appendChild(arrowShape);
  defs.appendChild(marker);
  svg.appendChild(defs);

  // Create curved path with marker
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  const pathData = `M ${startX} ${startY} Q ${midX} ${midY}, ${endX} ${endY}`;
  path.setAttribute('d', pathData);
  path.setAttribute('marker-end', 'url(#arrowhead-dynamic)');

  svg.appendChild(path);
  document.body.appendChild(svg);
}

// Remove arrow and mark as dismissed
function removeArrow() {
  const existingArrow = document.getElementById('dynamic-arrow-svg');
  if (existingArrow) {
    existingArrow.remove();
  }
  sessionStorage.setItem('arrowDismissed', 'true');
}

// Handle SPA navigation - check for arrow on all navigation events
function handleNavigation() {
  setTimeout(drawDynamicArrow, 100);
}

// Initial draw
document.addEventListener('DOMContentLoaded', handleNavigation);

// Listen for SPA navigation events
document.addEventListener('DOMContentLoaded', function() {
  // MkDocs Material uses instant navigation
  // Listen for navigation events
  document$.subscribe(handleNavigation);

  // Watch for search box interaction
  const searchInput = document.querySelector('.md-search__input');
  const searchToggle = document.querySelector('[data-md-toggle="search"]');

  if (searchInput) {
    searchInput.addEventListener('focus', removeArrow);
    searchInput.addEventListener('input', removeArrow);
  }

  if (searchToggle) {
    searchToggle.addEventListener('change', function() {
      if (this.checked) {
        removeArrow();
      }
    });
  }

  // Also watch for clicks on search button/icon
  const searchButton = document.querySelector('.md-search__icon[for="__search"]');
  if (searchButton) {
    searchButton.addEventListener('click', removeArrow);
  }
});

// Also handle resize and scroll (but only if not dismissed)
// Track viewport width to detect actual width changes
let lastWidth = window.innerWidth;

function checkAndRedraw() {
  const currentWidth = window.innerWidth;
  if (currentWidth !== lastWidth) {
    lastWidth = currentWidth;
    drawDynamicArrow();
  }
}

// Multiple methods to catch resize events
window.addEventListener('resize', function() {
  checkAndRedraw();
});

// Use matchMedia to detect responsive breakpoint changes
const mobileQuery = window.matchMedia('(max-width: 58.109375em)');
const tabletQuery = window.matchMedia('(min-width: 58.125em)');

mobileQuery.addEventListener('change', function() {
  setTimeout(drawDynamicArrow, 100);
});

tabletQuery.addEventListener('change', function() {
  setTimeout(drawDynamicArrow, 100);
});

window.addEventListener('scroll', drawDynamicArrow);

// Watch for page width changes more granularly
if (window.ResizeObserver) {
  const resizeObserver = new ResizeObserver(function(entries) {
    for (let entry of entries) {
      if (entry.contentBoxSize) {
        checkAndRedraw();
      }
    }
  });
  resizeObserver.observe(document.documentElement);
}

// Also check periodically when on index page (lightweight fallback)
let checkInterval;
function startPeriodicCheck() {
  if (document.getElementById('recommend-text')) {
    checkInterval = setInterval(checkAndRedraw, 500);
  } else {
    clearInterval(checkInterval);
  }
}

document.addEventListener('DOMContentLoaded', startPeriodicCheck);
document$.subscribe(startPeriodicCheck);

Extra.css#

Here is my custom CSS for the site:

extra.css
/docs/stylesheets/extra.css
/* Ignore the awful CSS, lots of plug and chug */

.md-grid, .md-content__inner {
  max-width: 960px;

  /* 101 char plaintext */
  /* max-width: 1220px;  */

  /* 140 char plaintext */
  /* max-width: 1440px; */
}

/* Hide Scrolling Headers in TOC condense mode */
li.md-nav__item.md-nav__item--active nav.md-nav.md-nav--secondary {
  display: none;
}

/* Make MD ": " create quick "<br>&emsp;" look */
.md-typeset dd, dl > dd > p {
  text-indent: 2em;
  margin-bottom: 0.3em;
  margin-top: 0.3em;
  text-indent: 0.4em hanging;
}

p, dd, dt {
  text-indent: 0.4em hanging;
}

dl > dt {
  margin-top: 0.8em;
}

/* Make code blocks aim for ~50% of max width, but expand if content is wider */
.note, .md-typeset pre, .admonition, .example, hr, .tabbed-set {
  max-width: 100%;
  min-width: 65%;
  width: fit-content;
  overflow-x: auto;
  box-sizing: border-box;
}

details.note, details.example, .notes, .md-typeset .admonition, .md-typeset details, .md-typeset table:not([class]) {
  font-size: 95%;
}

.md-content__inner * > img:not([id]):not([class]) {
  max-width: 85%;
  width: fit-content;
  overflow-x: auto;
  box-sizing: border-box;
  border: 2px solid;
  border-radius: 2%;
  transition: transform 0.1s ease;
}

.md-post__content {
  max-width: 80%;
}

.md-content__inner > p {
  max-width: 90%;
  width: fit-content;
  overflow-x: auto;
}

/* Ensure the code element inside also fits content */
.md-typeset pre > code {
  display: block;
}

/* Match filename width to code blocks */
.filename {
  max-width: 100%;
  min-width: 0%;
  width: fit-content;
  overflow-x: auto;
  box-sizing: border-box;
  font-family: monospace;
  font-weight: 100;
  /* text-decoration: underline; */
}

.filename::before {
  content: "📝 ";
}

.md-post__content > h2 > a::before {
  content: "📝 "
}

.md-sidebar__scrollwrap {
  padding-top: 15px
}

.md-clipboard {
  right: 0.5rem !important;
  top: 0.5rem !important;
}

.md-source__repository{
  display: none !important;
}

/* .md-sidebar--secondary {
  flex: 0 0 180px;
} */

/* Hide the entire source panel/side bar if present */
.md-source, .md-source__container, .md-source__repository, .md-source__toggle {
  display: none !important;
}

/* Fix: Prevent the Material theme from increasing base font-size at very large
   viewport widths (e.g. 1600px). The theme raises `html { font-size }` at
   `min-width:100em` and `125em` — lock it here to keep text consistent. */
html {
  font-size:125% !important;
}
@media screen and (min-width:100em) {
  html {
    font-size:125% !important;
  }
}
@media screen and (min-width:125em) {
  html {
    font-size:125% !important;
  }
}
.highlight .n {
  color: #ff76c8;
}

.highlight .c1 {
  color: #339b7d;
}

[data-md-color-scheme="default"] {
  .highlight .n {
    color: #ff1ea5;
  };
  .highlight .s {
    color: #00a151
  };
  .highlight .c1 {
    color: #579483;
  }
  --md-code-fg-color: #20272d;
}

/* Make logo bigger and adjust site title spacing */
.md-header__button.md-logo img,
.md-header__button.md-logo svg {
  height: 2.5rem;
  width: auto;
  transition: transform 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.8);
}

/* Fun spin animation on logo hover - overshoot and wobble back */
.md-header__button.md-logo:hover img,
.md-header__button.md-logo:hover svg {
  transform: rotate(360deg);
}

/* Apply same spin animation to home page logo */
#home-logo {
  transition: transform 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.8);
}

#home-logo:hover {
  transform: rotate(360deg);
}

/* Make home logo smaller on very small screens */
@media screen and (max-width: 530px) {
  #home-logo {
    width: 150px !important;
    height: auto;
  }
}

/* Make site title clickable and return to home */
.md-header__title {
  margin-left: 0.5rem;
}

.md-header__title .md-header__ellipsis {
  pointer-events: auto;
  cursor: pointer;
}

/* Wrap site title in a clickable link */
.md-header__title::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  cursor: pointer;
}

.md-header__title {
  position: relative;
}

/* JavaScript will handle the click */

/* Arrow pointing from #recommend-text to .md-search - dynamically positioned */
#recommend-text {
  position: relative !important;
  display: inline-block !important;
  padding: 2px 8px !important;
  border-radius: 4px !important;
}

/* Dynamic SVG arrow container */
#dynamic-arrow-svg {
  position: fixed !important;
  top: 0 !important;
  left: 0 !important;
  width: 100% !important;
  height: 100% !important;
  pointer-events: none !important;
  z-index: 9998 !important;
}

#dynamic-arrow-svg path {
  stroke: #ff6600 !important;
  stroke-width: 4 !important;
  fill: none !important;
  stroke-dasharray: 8, 4 !important;
  animation: dash-flow 4s linear infinite !important;
}

#dynamic-arrow-svg polygon {
  fill: #ff6600 !important;
}

@keyframes dash-flow {
  0% {
    stroke-dashoffset: 0;
  }
  100% {
    stroke-dashoffset: -12;
  }
}

/* Responsive: Hide arrow on smaller screens */
@media screen and (max-width: 58.109375em) {
  #recommend-text::after {
    display: none !important;
  }
}

.vscode Files#

Although not necessary, I use a few custom files in my .vscode project folder:

.vscode Tasks & Helper Scripts
/.vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Run MkDocs Server",
            "type": "shell",
            "command": "${workspaceFolder}/.venv/bin/mkdocs",
            "args": [
                "serve",
                "--livereload"
            ],
            "problemMatcher": [],
            "presentation": {
                "focus": false,
                "close": true,
                "reveal": "silent",
            },
            "runOptions": {
                "instanceLimit": 1,
                "instancePolicy": "terminateOldest"
            }
        },
        {
            "label": "Open Firefox (Site Root)",
            "type": "shell",
            "command": "sleep 2 && firefox http://127.0.0.1:8000",
            "windows": {
                "command": "powershell -Command \"Start-Sleep -Seconds 2; Start-Process firefox 'http://127.0.0.1:8000'\""
            },
            "osx": {
                "command": "sleep 2 && open -a Firefox http://127.0.0.1:8000"
            },
            "linux": {
                "command": "sleep 2 && firefox http://127.0.0.1:8000"
            },
            "problemMatcher": [],
            "presentation": {
                "focus": false,
                "close": true,
                "reveal": "silent",
            },
            "runOptions": {
                "instanceLimit": 1,
                "instancePolicy": "terminateOldest"
            }
        },
        {
            "label": "Open Firefox (Active Page)",
            "type": "shell",
            "command": "python .vscode/open_mkdocs_page.py ${file}",
            "problemMatcher": [],
            "presentation": {
                "focus": false,
                "close": true,
                "reveal": "silent",
            },
            "runOptions": {
                "instanceLimit": 1,
                "instancePolicy": "terminateOldest"
            }
        },
        {
            "label": "Run MkDocs Server + Open Firefox (Site Root)",
            "dependsOn": [
                "Run MkDocs Server",
                "Open Firefox (Site Root)"
            ],
            "dependsOrder": "parallel",
            "problemMatcher": []
        },
        {
            "label": "Run MkDocs Server + Open Firefox (Active Page)",
            "dependsOn": [
                "Run MkDocs Server",
                "Open Firefox (Active Page)"
            ],
            "dependsOrder": "parallel",
            "problemMatcher": []
        },
    ]
}

Here's a helper script I have to open the active file in VSCode in the web browser:

/.vscode/open_mkdocs_page.py
import os
import sys
import time
import webbrowser

# Delay before opening (in seconds)
DELAY_SECONDS = 2
time.sleep(DELAY_SECONDS)

# MkDocs config
BASE_URL = "http://127.0.0.1:8000"
DOCS_DIR = "docs"

# File passed from VS Code
file_path = sys.argv[1]
file_path = os.path.abspath(file_path)

# Project root = current working directory
project_root = os.getcwd()
docs_path = os.path.join(project_root, DOCS_DIR)

# Ensure file is inside docs/
if not file_path.startswith(docs_path):
    print("Not a MkDocs page (not inside docs/)")
    sys.exit(1)

# Compute relative path inside docs/
relative = os.path.relpath(file_path, docs_path)

# Convert Markdown file path → MkDocs URL path
url_path = relative.replace("\\", "/").replace(".md", "/")

# Final URL
full_url = f"{BASE_URL}/{url_path}"

print("Opening:", full_url)
webbrowser.open(full_url)

Here is an example of me using tasks to quickly run and navigate:

Here are my MkDocs Markdown snippets.

If you use a notes extension for capturing outside of repo, put this in your global snippets instead.

Markdown Snippets
/.vscode/mkdocs-snippets.code-snippets
{
    "Title (Doc)": {
        "scope": "markdown",
        "prefix": "Title (Doc)",
        "body": [
            "---",
            "title: ${1:$TM_FILENAME_BASE}",
            "description: ${2:Google Search / SEO}",
            "date:",
            "    created: $CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE",
            "    updated: $CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE",
            "---",
            "$0"
        ],
        "description": "mkdocs title"
    },
    "Title (Blog)": {
        "scope": "markdown",
        "prefix": "Title (Blog)",
        "body": [
            "---",
            "title: ${1:$TM_FILENAME_BASE}",
            "description: ${2:Google Search / SEO}",
            "author: ComfyTechTips",
            "date:",
            "    created: $CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE",
            "    updated: $CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE",
            "slug: $TM_FILENAME_BASE",
            "---",
            "",
            "${3:Top of document and hook}",
            "",
            "<!-- more -->",
            "",
            "${4:Hidden content}",
            "$0"
        ],
        "description": "mkdocs blog post with author and slug"
    },
    "Warning Box": {
        "scope": "markdown",
        "prefix": "Warning Box",
        "body": [
            "!!! warning",
            "    ${1:This will appear as a warning.}",
            "$0"
        ],
        "description": "mkdocs warning admonition"
    },
    "Note Box (Collapsed, Inline End, Custom)": {
        "scope": "markdown",
        "prefix": "Note Box (Collapsed)",
        "body": [
            "??? note inline end \"${1:My Warning}\"",
            "    ${2:Careful, this code can be very dangerous if ran without prior caution.}",
            "$0"
        ],
        "description": "mkdocs collapsible note inline end with custom title"
    },
    "Success Box (No Title)": {
        "scope": "markdown",
        "prefix": "Success Box (No Title)",
        "body": [
            "!!! success \"\"",
            "    ${1:RESULT}",
            "$0"
        ],
        "description": "mkdocs success box without title"
    },
    "Code Block (Highlight Lines)": {
        "scope": "markdown",
        "prefix": "Code Block (Highlight Lines)",
        "body": [
            "```${1:python} linenums=\"1\" hl_lines=\"${2:2 3}\"",
            "${3:def bubble_sort(items):",
            "    for i in range(len(items)):",
            "        for j in range(len(items) - 1 - i):}",
            "```",
            "$0"
        ],
        "description": "code block with line numbers and highlighted lines"
    },
    "Code Block (With Title)": {
        "scope": "markdown",
        "prefix": "Code Block (With Title)",
        "body": [
            "```${1:python} title=\"${2:bubble_sort.py}\"",
            "${3:def bubble_sort(items):",
            "    for i in range(len(items)):",
            "        for j in range(len(items) - 1 - i):",
            "            if items[j] > items[j + 1]:",
            "                items[j], items[j + 1] = items[j + 1], items[j]}",
            "```",
            "$0"
        ],
        "description": "code block with title"
    },
    "Multi-OS Tabs": {
        "scope": "markdown",
        "prefix": "Code Block Multi-OS",
        "body": [
            "=== \"${1:Windows}\"",
            "",
            "    ${2:Windows way:}",
            "",
            "    ```${3:powershell}",
            "    ${4:Write-Host \"Hello\"}",
            "    ```",
            "",
            "=== \"${5:Ubuntu Linux}\"",
            "",
            "    ${6:Another way to say hello world:}",
            "",
            "    ```${7:bash}",
            "    ${8:echo -e \"Hello\"}",
            "    ```",
            "$0"
        ],
        "description": "multi-OS tabbed content blocks"
    },
    "Multi-Language Tabs": {
        "scope": "markdown",
        "prefix": "Code Block Multi-Language",
        "body": [
            "=== \"${1:Python}\"",
            "",
            "    ${2:To say hello:}",
            "",
            "    ```${3:python}",
            "    ${4:print(\"Hello\")}",
            "    ```",
            "",
            "=== \"${5:C#}\"",
            "",
            "    ${6:Another way to say hello world:}",
            "",
            "    ```${7:csharp}",
            "    ${8:using System;",
            "",
            "    namespace HelloWorld",
            "    {",
            "        class Program",
            "        {",
            "            static void Main(string[] args)",
            "            {",
            "                Console.WriteLine(\"Hello, World!\");",
            "            }",
            "        }",
            "    }}",
            "    ```",
            "$0"
        ],
        "description": "multi-language tabbed code blocks"
    },
    "Table": {
        "scope": "markdown",
        "prefix": "Table",
        "body": [
            "| ${1:Product}      | ${2:What}                                 | ${3:Link} |",
            "| :----------- | :----------------------------------- | :--- |",
            "| ${4:`GET`}        | ${5::material-check:     Fetch resource}  | ${6:<>}   |",
            "| ${7:`PUT`}        | ${8::material-check-all: Update resource} | ${9:<>}   |",
            "| ${10:`DELETE`}     | ${11::material-close:     Delete resource} | ${12:<>}   |",
            "$0"
        ],
        "description": "markdown table with icons"
    },
    "Image (Centered with Caption)": {
        "scope": "markdown",
        "prefix": "Image (Centered)",
        "body": [
            "<figure>",
            "  <img src=\"${1:https://dummyimage.com/600x400/eee/aaa}\" width=\"${2:300}\" />",
            "  <figcaption>${3:Image caption}</figcaption>",
            "</figure>",
            "$0"
        ],
        "description": "centered image with caption"
    },
    "Nav Config (.nav.yml)": {
        "scope": "yaml",
        "prefix": "Nav Config Yaml",
        "body": [
            "title: ${1:Lorem Ipsum} # Custom title for .nav.yml root directory",
            "hide: ${2:false} # Hides .nav.yml root directory",
            "ignore: \"${3:*.hidden.md}\" # Hides pattern for files matching",
            "append_unmatched: ${4:true} # Anything that isn't explicitly caught will go to end",
            "",
            "sort:",
            "  direction: ${5:asc}",
            "  type: ${6:natural}",
            "  by: ${7:title} # some gotchas, read documentation",
            "  sections: ${8:last}",
            "  ignore_case: ${9:true}",
            "",
            "nav:",
            "  - ${10:one-i-want-first.md}",
            "  - \"*\" # the remaining files before directories and sections",
            "  - ${11:Custom Title: Directory}",
            "  - ${12:Custom Title: File.md}",
            "$0"
        ],
        "description": "mkdocs awesome-pages .nav.yml configuration"
    }
}

That is all!