schnerring / hugo-theme-gruvbox

A retro-looking Hugo theme inspired by gruvbox. The pastel colors are high contrast, easily distinguishable, pleasing to the eye, and feature light and dark color palettes.
MIT License
196 stars 50 forks source link
dark-mode flexsearch gruvbox hugo hugo-theme jsonresume theme

Gruvbox Hugo Theme

A retro-looking Hugo theme inspired by gruvbox to build secure, fast, and SEO-ready websites.

This theme is easily customizable with features that any coder loves.

I took a lot of inspiration from the Hello Friend and Doks Hugo themes.

DEMO https://hugo-theme-gruvbox.schnerring.net/

Screenshot of the theme in dark and light colors

DISCLAIMER: Project Status

This theme is still in early development. Check out the issues to see what's still missing.

Highlights

A big thank you to the authors of the software that make this theme possible! ❤️

Quickstart

The theme requires extended Hugo because it uses Sass/SCSS. You'll also have to install Go because the theme uses Go modules.

  1. git clone the repository and cd into it
  2. Run npm ci to install the dependencies
  3. Run hugo server

Install The Theme

Create a new Hugo website:

hugo new site example.com
cd example.com/

Initialize the site as Hugo module

hugo mod init example.com

Add the following to the hugo.toml file:

[markup]
  # (Optional) To be able to use all Prism plugins, the theme enables unsafe
  # rendering by default
  #_merge = "deep"

[build]
  # Merge build config of the theme
  _merge = "deep"

# This hopefully will be simpler in the future.
# See: https://github.com/schnerring/hugo-theme-gruvbox/issues/16
[module]
  [[module.imports]]
    path = "github.com/schnerring/hugo-theme-gruvbox"
  [[module.imports]]
    path = "github.com/schnerring/hugo-mod-json-resume"
    [[module.imports.mounts]]
      # This will add the sample Richard Hendricks CV data
      source = "data"
      target = "data"
    [[module.imports.mounts]]
      source = "layouts"
      target = "layouts"
    [[module.imports.mounts]]
      source = "assets/css/json-resume.css"
      target = "assets/css/critical/44-json-resume.css"
  [[module.mounts]]
    # required by hugo-mod-json-resume
    source = "node_modules/simple-icons/icons"
    target = "assets/simple-icons"
  [[module.mounts]]
    source = "assets"
    target = "assets"
  [[module.mounts]]
    source = "layouts"
    target = "layouts"
  [[module.mounts]]
    source = "static"
    target = "static"
  [[module.mounts]]
    source = "node_modules/prismjs"
    target = "assets/prismjs"
  [[module.mounts]]
    source = "node_modules/prism-themes/themes"
    target = "assets/prism-themes"
  [[module.mounts]]
    source = "node_modules/typeface-fira-code/files"
    target = "static/fonts"
  [[module.mounts]]
    source = "node_modules/typeface-roboto-slab/files"
    target = "static/fonts"
  [[module.mounts]]
    source = "node_modules/@tabler/icons/icons"
    target = "assets/tabler-icons"
  [[module.mounts]]
    # Add hugo_stats.json to Hugo's server watcher
    source = "hugo_stats.json"
    target = "assets/watching/hugo_stats.json"

Install the theme:

hugo mod get

Initialize the NPM package.json and install the dependencies:

hugo mod npm pack
npm install

Run Hugo:

hugo server

Update The Theme

Update the Hugo modules:

hugo mod get -u
hugo mod tidy

Update the NPM dependencies:

hugo mod npm pack
npm install

Colors

Two options are available to configure the theme colors:

Prism

The theme allows customization of Prism via hugo.toml parameters:

[params]
  [params.prism]
    languages = [
      "markup",
      "css",
      "clike",
      "javascript"
    ]
    plugins = [
      "normalize-whitespace",
      "toolbar",
      "copy-to-clipboard"
    ]

In my opinion, this is the coolest feature of the theme. Other Hugo themes usually include a pre-configured version of Prism, which complicates updates and change tracking, and clutters the theme's code base with third-party JavaScript.

The Prism theme is not configurable because of the integration with the dark mode functionality. Toggling between color modes swaps the Prism theme between gruvbox-dark and gruvbox-light from github.com/PrismJS/prism-themes.

Check out the Prism showcase on the Demo site for examples

Explore Prism Features

After running npm install, explore Prism features like this:

# Languages
ls node_modules/prismjs/components

# Plugins
ls node_modules/prismjs/plugins

Image Optimization

Images are optimized by default without requiring shortcodes. A custom render hook does all the heavy lifting (see render-image.html).

By default, the theme creates resized versions of images ranging from 300 to 700 pixels wide in increments of 100 pixels.

If the image format is not WebP, the image is converted. The original file format will serve as a fallback for browsers that don't support the WebP format.

Note that only images that are part of the page bundle are processed. If served from the static/ directory or external sources, the image will be displayed but not be processed.

Additionally, all images are lazily loaded to save the bandwidth of your users.

Configuration

The default quality is 75%. See the official Image Processing Config Hugo docs. Change it by adding the following to the hugo.toml file:

[imaging]
  quality = 75

Change the resize behavior:

[params]
  [params.imageResize]
    min = 300
    max = 700
    increment = 100

Captions

![Alt text](image-url.jpg "Caption with **markdown support**")

The demo site features examples you can look at. I also use the theme for my website.

Blog Post Covers

Add blog post covers by defining them in the front matter of your posts:

---
cover:
  src: my-blog-cover.jpg
  alt: A beautiful image containing interesting things
  caption: [Source](https://www.flickr.com/)
---

Embed Video Files

Use the video shortcode to embed your video files from Page Resources.

With a page bundle looking like the following:

embed-videos/
|-- index.md
|-- my-video.jpg
|-- my-video.mp4
|-- my-video.webm

You can embed my-video like this:

{{< video src="https://github.com/schnerring/hugo-theme-gruvbox/raw/main/my-video" autoplay="true" controls="false" loop="true" >}}

The shortcode looks for media files matching the filename my-video*. For each video MIME type file, a <source> element is added. The first image MIME type file is used as poster (thumbnail). It will render the following HTML:

<video
  autoplay
  loop
  poster="/blog/embed-videos/my-video.jpg"
  width="100%"
  playsinline
>
  <source src="https://github.com/schnerring/hugo-theme-gruvbox/raw/main/blog/embed-videos/my-video.mp4" type="video/mp4" />
  <source src="https://github.com/schnerring/hugo-theme-gruvbox/raw/main/blog/embed-videos/my-video.webm" type="video/webm" />
</video>

You can set a Markdown caption, wrapping the <video> inside a <figure>.

Additionally, the shortcode allows you to set the following attributes:

Attribute Default
autoplay false
controls true
height
loop false
muted true
preload
width 100%
playsinline true

Learn more about the <video> attributes here.

SEO

Due to the European Copyright Directive it is required to opt into displaying snippets in search engine results.

By default, every page (except 404) includes the index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1 robots meta value, opting into all snippet features.

You can override the robots meta value in the front matter of your pages:

---
robots: noindex, nofollow
---

Social Share Links

Configure social share links in the Hugo config like this:

[params]
  [[params.socialShare]]
    iconSuite = "simple-icon"
    iconName = "facebook"
    formatString = "https://www.facebook.com/sharer.php?u={url}"
  [[params.socialShare]]
    iconSuite = "simple-icon"
    iconName = "reddit"
    formatString = "https://reddit.com/submit?url={url}&title={title}"
  [[params.socialShare]]
    iconSuite = "tabler-icon"
    iconName = "outline/mail"
    formatString = "mailto:?subject={title}&body={url}"

Use the iconSuite setting to specify the icon suite used for the social share link: simple-icon or tabler-icon. Select an icon from the suite with the iconName setting. Tabler icons come in two distinct styles, filled and outline. You'll have to the prefix the iconName accordingly, e.g. iconName = "outline/sun".

The formatString supports the following placeholders:

To enable social share links, set the following in the post's front matter:

---
socialShare: true
---

Check out the Social Share URLs repo on GitHub for more format strings.

Favicon

The favicons and corresponding markup were generated with the free RealFaviconGenerator.net.

The easiest way to replace the default favicons is to generate them using RealFaviconGenerator.net and put the generated files into the static/ directory.

Extensibility

You can extend the theme by overriding the following partials in the layouts/partials directory which by default are empty placeholder files:

Example: Adding KaTeX Support to the Theme

KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web. Let's add it to the theme via npm. First, add the following to the package.hugo.json file:

"dependencies": {
  "katex": "^0.16.8"
}

Then run hugo mod npm pack to sync the package.hugo.json dependencies with package.json. Run npm install after. We then need to mount the node_modules/katex folder into Hugo's virtual filesystem by adding the following to the config/_default/module.toml file:

[[mounts]]
  source = "node_modules/katex"
  target = "assets/katex"

We can then add the following to layouts/partials/head/head_end.html:

{{ if .Params.katex }}
  {{ $katexCSS := resources.Get "katex/dist/katex.min.css" }}
  <link
    rel="stylesheet"
    href="https://github.com/schnerring/hugo-theme-gruvbox/blob/main/{{ $katexCSS }}"
    {{ if hugo.IsProduction }}
      integrity="{{ $katexCSS.Data.Integrity }}"
    {{ end }}
    crossorigin="anonymous"
  />

  {{ $katexJS := resources.Get "katex/dist/katex.min.js" }}
  <script
    defer
    src="https://github.com/schnerring/hugo-theme-gruvbox/raw/main/{{ $katexJS.RelPermalink }}"
    {{ if hugo.IsProduction }}
      integrity="{{ $katexJS.Data.Integrity }}"
    {{ end }}
    crossorigin="anonymous"
  ></script>

  {{ $autoRender := resources.Get "katex/dist/contrib/auto-render.min.js" }}
  <script
    defer
    src="https://github.com/schnerring/hugo-theme-gruvbox/raw/main/{{ $autoRender.RelPermalink }}"
    {{ if hugo.IsProduction }}
      integrity="{{ $autoRender.Data.Integrity }}"
    {{ end }}
    crossorigin="anonymous"
    onload="renderMathInElement(document.body);"
  ></script>
{{ end }}

The only thing left is enabling KaTeX in the front matter of our content:

---
title: "Hello World"
description: "The first post of this blog"
date: 2021-03-14T15:00:21+01:00
draft: false
katex: true
---

I'm a .NET developer by trade, so let's say hello in C#!

Configure the Tag Cloud

The theme comes with a tag cloud partial. It is included in the sidebar, but it is disabled by default. If you wish to configure it, add the following to the [params] section in the hugo.toml file:

[params.tagCloud]
  enable = true
  minFontSizeRem = 0.8
  maxFontSizeRem = 2.0

Remove the Sidebar

If you want to get rid of the sidebar, add an empty data/json_resume/en.json file with the following content:

{
  "$schema": "https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json",
  "basics": {},
  "work": [],
  "volunteer": [],
  "education": [],
  "awards": [],
  "certificates": [],
  "publications": [],
  "skills": [],
  "languages": [],
  "interests": [],
  "references": [],
  "projects": [],
  "meta": {
    "canonical": "https://raw.githubusercontent.com/jsonresume/resume-schema/master/resume.json",
    "version": "v1.0.0",
    "lastModified": "2017-12-24T15:53:00"
  }
}

Extend CSS

The theme uses PostCSS with following plugins:

Additionally the following plugins are used if building the site with hugo -e production:

Inside the assets/css two folders exist, critical and non-critical. Files inside critical are concatenated during build time and inlined into the <head> element. The styles target mostly above the fold content. Try to keep inline CSS to a minimum because it can't be cached and will be inlined into every single page. Files inside non-critical are concatenated into a single file and included as <style>. Most of the styles are in there.

Files are concatenated in lexicographic order of their file names. File names start with two digits and a hyphen: NN-. The order of files might differ between Linux and Windows, so using this convention improves cross-platform compatibility. You might know this approach if you're familiar with Xorg.

You can add new CSS files to the PostCSS pipeline like this: