This commit is contained in:
Adrian Amaglio 2023-11-17 10:54:41 +01:00
commit c09f092db4
144 changed files with 5271 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
# Generated files by hugo
/public/
/resources/_gen/
/assets/jsconfig.json
hugo_stats.json
# Executable may be added to repository
hugo.exe
hugo.darwin
hugo.linux
# Temporary lock file while building
/.hugo_build.lock
content

5
archetypes/default.md Normal file
View File

@ -0,0 +1,5 @@
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++

1
assets/custom.css Normal file
View File

@ -0,0 +1 @@
/* Add custom CSS */

1
assets/custom.js Normal file
View File

@ -0,0 +1 @@
// Add custom JS

214
config/_default/hugo.toml Normal file
View File

@ -0,0 +1,214 @@
### General
baseURL = '' # Enter your full production URL
languageCode = 'fr-fr' # Default
timeZone = 'Europe/Paris' # IANA timezone https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
title = 'RimaRima' # Site title used throughout site
### SEO, Analytics & 3rd-party
enableRobotsTXT = true # To overwrite the theme's robots.txt, add your own in the /layouts/ directory
rssLimit = -1 # -1 is unlimited RSS entries
# googleAnalytics = '' # Enter GA tracking ID to enable GA4
# disqusShortname = '' # Enter Disqus shortname to enable comments
### Content & Publishing
# Please see https://gohugo.io/getting-started/configuration/ for detailed explanations
buildDrafts = false # Default
buildExpired = false # Default
buildFuture = false # Default
canonifyURLs = false # Default
defaultContentLanguage = 'en' # Default
disableAliases = true # Set to true if using server (Netlify, .htaccess) for redirects instead of Hugo
disableKinds = [] # Default
enableEmoji = true # Use Emojis in content
enableGitInfo = false # Default, enable to use git for lastmod (can be overwritten in frontmatter)
enableInlineShortcodes = false # Default, false is more security-friendly
ignoreFiles = [] # Default
# newContentEditor = 'code' # Set VS Code as default editor
paginate = 10 # Default
paginatePath = 'page' # Default
pluralizeListTitles = true # Default
publishDir = 'public' # Default
relativeURLs = false # Default
summaryLength = 70 # Default
titleCaseStyle = 'AP' # Default, other options: Chicago (slightly different) or Go (all first letters capitalized)
### Other
archetypeDir = 'archetypes' # Default
assetDir = 'assets' # Default
contentDir = 'content' # Default
dataDir = 'data' # Default
disableHugoGeneratorInject = false # Default
disableLiveReload = false # Default
# Use this theme as git submodule
theme = 'hugo-liftoff'
# Or, use this theme as hugo module
# [module]
# [[module.imports]]
# path = 'hugo-liftoff'
[taxonomies]
tag = 'tags'
category = 'categories'
series = 'series' # Allows you to create an organized series of posts (e.g. multi-part tutorial)
project-type = 'project types' # Categorize projects by type (e.g. client work, personal, open source, etc.)
[permalinks]
[permalinks.page]
# e.g. /subsection/example-post instead of /posts/subsection/example-post
posts = '/:sections[last]/:slug/' # Removes 'posts' from the permalink structure for posts created under nested sub-sections
[permalinks.section]
# e.g. /subsection/ instead of /posts/subsection/
posts = '/:slug/'
[markup]
defaultMarkdownHandler = 'goldmark' # Default (everything under [markup] is unless otherwise specified)
[markup.goldmark]
[markup.goldmark.extensions]
definitionList = true
footnote = true
linkify = true
linkifyProtocol = 'https'
strikethrough = true
table = true
taskList = true
typographer = true
[markup.goldmark.parser]
autoHeadingID = true
autoHeadingIDType = 'github'
[markup.goldmark.parser.attribute]
block = false
title = true
[markup.goldmark.renderer]
hardWraps = false
unsafe = false
xhtml = false
[markup.highlight]
anchorLineNos = false
codeFences = true
guessSyntax = false
hl_Lines = ''
lineAnchors = ''
lineNoStart = 1
lineNos = true # Not the default
lineNumbersInTable = false # Not the default
noClasses = true
noHl = false
style = 'monokai'
tabWidth = 4
[markup.tableOfContents]
endLevel = 3
ordered = false
startLevel = 2
[related]
# Default related posts settings
includeNewer = false
threshold = 80
toLower = false
[[related.indices]]
name = 'keywords'
weight = 100
[[related.indices]]
name = 'date'
weight = 10
# Remove if not using tags taxonomy
[[related.indices]]
name = 'tags'
weight = 80
[sitemap]
# Default sitemap settings
changefreq = 'monthly'
filename = 'sitemap.xml'
priority = 0.5
[frontmatter]
# Default frontmatter date settings
date = ['date', 'publishDate', 'lastmod']
expiryDate = ['expiryDate']
lastmod = ['lastmod', ':git', 'date', 'publishDate']
publishDate = ['publishDate', 'date']
[caches]
# Default cache settings
[caches.assets]
dir = ':resourceDir/_gen'
maxAge = -1
[caches.getcsv]
dir = ':cacheDir/:project'
maxAge = -1
[caches.getjson]
dir = ':cacheDir/:project'
maxAge = -1
[caches.images]
dir = ':resourceDir/_gen'
maxAge = -1
[caches.modules]
dir = ':cacheDir/modules'
maxAge = -1
[imaging]
# Default image processing settings
anchor = 'Smart'
bgColor = '#ffffff'
hint = 'photo'
quality = 75
resampleFilter = 'Box'
### Hugo Pipes
[minify]
disableCSS = false
disableHTML = false
disableJS = false
disableJSON = false
disableSVG = false
disableXML = false
minifyOutput = false
[minify.tdewolff]
[minify.tdewolff.css]
keepCSS2 = true
precision = 0
[minify.tdewolff.html]
keepComments = false
keepConditionalComments = true
keepDefaultAttrVals = true
keepDocumentTags = true
keepEndTags = true
keepQuotes = false
keepWhitespace = false
[minify.tdewolff.js]
keepVarNames = false
noNullishOperator = false
precision = 0
[minify.tdewolff.json]
keepNumbers = false
precision = 0
[minify.tdewolff.svg]
precision = 0
[minify.tdewolff.xml]
keepWhitespace = false
### Netlify settings
# add redirects/headers
[outputs]
home = ["HTML", "RSS", "REDIRECTS", "HEADERS"]
# remove .{ext} from text/netlify
[mediaTypes."text/netlify"]
suffixes = [""]
delimiter = ""
# add output format for netlify _redirects
[outputFormats.REDIRECTS]
mediatype = "text/netlify"
baseName = "_redirects"
isPlainText = true
notAlternative = true
# add output format for netlify _headers
[outputFormats.HEADERS]
mediatype = "text/netlify"
baseName = "_headers"
isPlainText = true
notAlternative = true

View File

@ -0,0 +1,84 @@
[[main]]
# Top-level menu entry
identifier = "work"
name = "Work"
url = "/projects/"
weight = 1
[[main]]
identifier = "projects"
name = "Projects"
url = "/projects/"
parent = "work"
weight = 1
[[main]]
identifier = "about"
name = "About"
url = "/about/"
parent = "work"
weight = 2
[[main]]
identifier = "contact"
name = "Contact"
url = "/contact/"
parent = "work"
weight = 3
[[main]]
# Top-level menu entry
identifier = "writing"
name = "Writing"
url = "/posts/"
weight = 4
[[main]]
identifier = "posts"
name = "All Posts"
url = "/posts/"
parent = "writing"
weight = 4
[[main]]
identifier = "subsection"
name = "Subsection"
url = "/subsection/"
# url = "/posts/subsection/" # Remove url param from subsection/_index.md frontmatter to use full permalink
parent = "writing"
weight = 5
[[main]]
# Top-level menu entry
identifier = "explore"
name = "Explore"
url = "/categories/"
weight = 6
[[main]]
identifier = "categories"
name = "Categories"
url = "/categories/"
parent = "explore"
weight = 6
[[main]]
identifier = "tags"
name = "Tags"
url = "/tags/"
parent = "explore"
weight = 7
[[main]]
identifier = "series"
name = "Series"
url = "/series/"
parent = "explore"
weight = 8
[[main]]
identifier = "project-type"
name = "Project Types"
url = "/project-types/"
parent = "explore"
weight = 9

View File

@ -0,0 +1,65 @@
### SEO, Analytics & 3rd-party
netlify_forms = true # add data-netlify attribute to contact and newsletter forms, false to disable Netlify forms
# gtm_id = '' # GTM tracking (leave blank to use GA)
disallow_search_engines = false # Disallow search engine crawling in robots.txt
### Content
favicon = true # Enable favicon, add your favicon files to /static/ directory
# avatar = 'avatar.png' # Custom avatar image in /assets/
grayscale_avatar = true # Add a grayscale filter to the avatar image
author = 'John Doe' # Default author for <head> meta
description = "Personal blog and portfolio site of John Doe." # Add a global meta description to <head>
footer_text = "Built with [Hugo Liftoff](https://github.com/wjh18/hugo-liftoff) theme." # Customize footer text
fallback_text = "No content available yet. Coming soon." # Fallback text for empty content
label_drafts = true # Add a label indicator next to the title of any built drafts (dev only)
# Customize newsletter text
newsletter_header = "This is my custom newsletter header with a captivating title."
newsletter_description = "This is my custom newsletter description that tells you why you should sign up."
newsletter_submit = "Join now"
global_newsletter = false # Enable newsletter site-wide
# CSS / JavaScript
enable_postcss = false # Enable if you're using npm and want to utilize PostCSS processing
custom_css = 'custom.css' # Add the specified file to assets before setting this param to avoid errors
custom_js = 'custom.js' # Add the specified file to assets before setting this param to avoid errors
### Web schemas
# Open Graph
images = ['images/default.png'] # Fallback for Open Graph and Twitter Cards images if none are present in front matter. Enter path to the image.
ogLocale = "en_US" # Open graph locale
# Twitter Cards
twitterSite = "johndoestwitter" # Enter your twitter handle without the @
twitterCreator = "johndoestwitter" # Enter your twitter handle without the @
# JSON-LD structured data schemas
schemaName = "John Doe" # Enter your name
schemaLocale = "en-US" # Structured data locale
schemaImage = "images/default.png" # Image for Person structured data schema. Enter path to the image.
schemaImageWidth = 453 # Width of the above image
schemaImageHeight = 455 # Height of the above image
### Social
# Show social links in footer, home hero section, or about page
footer_socials = true
home_hero_socials = true
about_page_socials = true
# Enable or disable individual social icons
[social.links]
twitter = "username"
github = "username"
stack_overflow = "userid/username" # include user id
email = "user@example.com"
# linkedin = "username" # Comment out to disable
# mastodon_server = "example.social" # include subdomains if relevant, scheme not needed (defaults to https)
# mastodon_user = "username" # don't include preceding @
# Enable or disable individual social share icons in posts
[social.share]
#facebook = true
#linkedin = true
#twitter = true
#reddit = true
#email = true

View File

@ -0,0 +1,9 @@
# Adds custom security headers for development environment only
[[headers]]
for = '/**'
[headers.values]
# Content-Security-Policy = 'script-src localhost:1313'
Referrer-Policy = 'strict-origin-when-cross-origin'
X-Content-Type-Options = 'nosniff'
X-Frame-Options = 'DENY'
X-XSS-Protection = '1; mode=block'

View File

@ -0,0 +1,3 @@
### Config for production environment
# Includes but overwrites anything in _default/config.toml
# Excludes anything in _development/config.toml if it exists

View File

@ -0,0 +1,3 @@
### Params for production environment
# Includes but overwrites anything in _default/params.toml
# Excludes anything in _development/params.toml if it exists

3
hugo.toml Normal file
View File

@ -0,0 +1,3 @@
baseURL = ''
languageCode = 'fr-fr'
title = 'RimaRima'

0
static/.keep Normal file
View File

View File

@ -0,0 +1,12 @@
# Contributing
For any proposed changes or bug reports, please [open an issue](https://github.com/wjh18/hugo-liftoff/issues) first using one of the issue templates. Then feel free to work on it. You're also welcome to tackle any existing issues.
## How to contribute
* Fork the project on Github
* Clone the fork to your local machine
* Create a descriptive branch for the changes
* Make and commit your changes to the new branch
* Push your changes to the branch
* Submit a pull request on Github using the [pull request template](https://github.com/wjh18/hugo-liftoff/blob/master/.github/pull_request_template.md).

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2023 Will J. Holmes
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,169 @@
# Hugo Liftoff
**Warning: I no longer have time to work on this theme for various reasons. It's unlikely I'll get to your issue and no additional features are planned, although the theme will remain available to the public. Feel free to fork it if you need to make extensive changes. I will consider merging simple bugfix pull requests. Apologies for any inconvenience this may cause. Please see [v3.4.3 release notes](https://github.com/wjh18/hugo-liftoff/releases/tag/v3.4.3) for further details.**
## About
Hugo Liftoff is a minimal blog/portfolio theme with a focus on content creation and SEO best practices. It's an ideal choice for technical users jump-starting a personal brand.
## Documentation
The full documentation is hosted on this repo's [Github Wiki](https://github.com/wjh18/hugo-liftoff/wiki).
Please open an issue if you find any mistakes in the docs or have suggestions for improvement.
## Demo Site
A working [demo of the theme](https://hugo-liftoff.netlify.app/) is available to the public for evaluation.
The `exampleSite` content included with the theme, which you're free to copy into your own project, mirrors the content used in the demo. The idea behind this is to help you get started quickly and replicate any features you saw in the demo that you think could be useful in your own project.
## Notable Features
* Content subsections with the ability to filter recent posts on homepage by subsection
* Series posts taxonomy with single posts that list all posts in the current series
* Next/prev links at the end of single posts for subsections or series posts (if enabled)
* Light/dark mode toggle using CSS custom properties
* Mobile-responsive, collapsible JS menu with automatic submenu support based on menu config
* Customizable newsletter with Netlify Forms support and conditional visibility control
* And much more...
## Overview of Features
Below are the current features of this theme. Features labeled *optional* or *frontmatter* can be enabled/disabled in config or frontmatter, respectively.
### Netlify
* Custom headers / redirects with Netlify (optional)
* Netlify forms support for newsletter opt-in and contact page (optional)
* Sample `netlify.toml` file for streamlined deployment
### 3rd Party
* Google Analytics v4
* Google Tag Manager as an alternative to GA (optional)
* Disqus comments (optional)
* Disable comments on a per-page basis (optional) (frontmatter)
### Newsletter
* Newsletter opt-in with Netlify Forms support (optional) (frontmatter)
* Global display of newsletter opt-in (optional)
* Customizable newsletter header, description and CTA text (optional)
* Override global newsletter on a per-page basis (optional) (frontmatter)
* Enable or disable newsletter on a per-page basis (frontmatter)
### Social
* Native Twitter, Mastodon, Github, Stack Overflow, LinkedIn and email social links with SVG (optional)
* Enable or disable social links in footer, homepage hero, and about page (optional)
* Enable or disable individual social links (optional)
* Facebook, LinkedIn, Twitter, Reddit and email social share icons with SVG for posts (optional)
* Enable or disable individual social share icons (optional)
### SEO / RSS
* Enhanced Open Graph, Twitter Cards, and Schema.org templates
* RSS feed that excludes any pages outside of the posts section
* Customizable title and SEO title tags or use title for both (frontmatter)
* Customizable summary and meta description or use description for both (frontmatter)
* Custom author meta tag (optional) (frontmatter)
* Custom title tags and meta descriptions for every page (optional) (frontmatter)
* `robots.txt` and `sitemap.xml`
* Disable search engine crawling (optional)
### Series / Subsections
* Content subsections with the ability to filter recent posts on homepage by subsection (optional)
* Series posts taxonomy with single posts that list all posts in the current series (optional)
* Next/prev links at the end of single posts for subsections or series posts (if enabled)
* Subsection support for posts with custom permalinks for clean SEO URLs (optional)
* Mobile-responsive, collapsible JS menu with automatic submenu support based on menu config
### Homepage
* Customize hero title and subtitle on homepage (frontmatter)
* Customize posts and projects section headings on homepage (frontmatter)
### Posts & Projects
* Toggle-able sticky table of contents for posts (frontmatter)
* Related posts (frontmatter)
* Social share icons for posts (frontmatter)
* Reading time and word count for posts
* Last modified dates for posts (optional)
* Customizable live URL, source URL and tech stack details for projects (frontmatter)
* Tag and category taxonomies
* Project type taxonomy for categorizing projects
* Recent posts and projects on homepage
### Code snippets
* Syntax highlighting
* One-click copy button and language indicator for code snippets
### CSS / JS
* Light/dark mode toggle using CSS custom properties
* Add custom CSS / JS in `assets`
* CSS and JS minification
* Frontend build pipeline with ESBuild and ToCSS
* PostCSS processing for autoprefixing (optional)
* `npm` completely optional unless using PostCSS / Autoprefixer
* Fluid typography scale with CSS `clamp()`
* Sourcemaps for SCSS and JS in development
### Images
* Image processing with Hugo resources
* Feature images for posts and projects from `assets` or page bundle (frontmatter)
* Custom homepage hero avatar image (optional)
* Disable grayscale avatar filter (optional)
* Enable/disable favicons (optional)
### Archetypes
* Archetype templates for posts and projects
* Page bundle support for archetype templates
### Markdown Hooks
* Markdown render hooks for codeblocks, images, headings and links
* Language indicator for codeblocks
* Add custom CSS class to markdown images via URL fragments
* Heading anchor link SVG icon
### Other Content
* Create generic single pages quickly with the default single template (optional)
* Additional markdown footer text (optional)
* Add a label to drafts in development (optional)
* Responsive support for common markdown styles like tables
* About page with social links (optional)
* Contact page with Netlify Forms support (optional)
* Customize fallback text for empty content (optional)
### Hugo Defaults
* Example `hugo.toml` with the majority of Hugo config defaults included for easy customization
* Override config settings based on Hugo environment
* Built-in shortcodes
* And much more...Hugo has a lot to offer!
## Planned Features
The following features are planned for a future release.
* Real-time site search
* Image galleries for projects
* i18n support
* Additional advanced Google structured data schemas
* Custom shortcodes for things like project lists
## Getting Help
To submit a bug report, feature request, or usage questions, please open an [issue](https://github.com/wjh18/hugo-liftoff/issues) on Github using one of the issue templates.
## Contributing
If you'd like to contribute to the project, fork it and submit a pull request with your changes using the [pull request template](https://github.com/wjh18/hugo-liftoff/blob/master/.github/pull_request_template.md). Please see the [Contribution Guidelines](https://github.com/wjh18/hugo-liftoff/blob/master/CONTRIBUTING.md) for more details.

View File

@ -0,0 +1,13 @@
---
title: {{ replace .Name "-" " " | title }}
seo_title: {{ replace .Name "-" " " | title }}
description:
slug: {{ .Name }}
author: {{ .Site.Params.author }}
draft: true
date: {{ .Date }}
newsletter: true
---

View File

@ -0,0 +1,28 @@
---
title: {{ replace .Name "-" " " | title }}
seo_title: {{ replace .Name "-" " " | title }}
summary:
description:
slug: {{ .Name }}
author: {{ .Site.Params.author }}
draft: true
date: {{ .Date }}
lastmod:
expiryDate:
publishDate:
feature_image:
feature_image_alt:
categories:
tags:
series:
toc: true
related: true
social_share: true
newsletter: true
disable_comments: false
---

View File

@ -0,0 +1,28 @@
---
title: {{ replace .Name "-" " " | title }}
seo_title: {{ replace .Name "-" " " | title }}
summary:
description:
slug: {{ .Name }}
author: {{ .Site.Params.author }}
draft: true
date: {{ .Date }}
lastmod:
expiryDate:
publishDate:
feature_image:
feature_image_alt:
categories:
tags:
series:
toc: true
related: true
social_share: true
newsletter: true
disable_comments: false
---

View File

@ -0,0 +1,26 @@
---
title: {{ replace .Name "-" " " | title }}
seo_title: {{ replace .Name "-" " " | title }}
summary:
description:
slug: {{ .Name }}
author: {{ .Site.Params.author }}
draft: true
date: {{ .Date }}
lastmod:
expiryDate:
publishDate:
feature_image:
feature_image_alt:
project types:
techstack:
live_url:
source_url:
newsletter: true
---

View File

@ -0,0 +1,26 @@
---
title: {{ replace .Name "-" " " | title }}
seo_title: {{ replace .Name "-" " " | title }}
summary:
description:
slug: {{ .Name }}
author: {{ .Site.Params.author }}
draft: true
date: {{ .Date }}
lastmod:
expiryDate:
publishDate:
feature_image:
feature_image_alt:
project types:
techstack:
live_url:
source_url:
newsletter: true
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -0,0 +1,3 @@
import { switcher, clipboard, toggleToc } from './components/components';
import { header } from './layouts/header';
import { home } from './pages/home';

View File

@ -0,0 +1,44 @@
// Adapted from the following tutorials:
// https://www.dannyguo.com/blog/how-to-add-copy-to-clipboard-buttons-to-code-blocks-in-hugo/
// https://aaronluna.dev/blog/add-copy-button-to-code-blocks-hugo-chroma/
// https://logfetch.com/hugo-add-copy-to-clipboard-button/
const addCopyButtons = (clipboard) => {
// 1. Look for pre > code elements in the DOM
document.querySelectorAll('.highlight > pre > code').forEach((codeBlock) => {
// 2. Create a button that will trigger a copy operation
const button = document.createElement('button');
const svgCopy = '<svg role="img" aria-hidden="true" aria-labelledby="clipboardCopy" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><title id="clipboardCopy">Copy the code snippet contents</title><path fill-rule="evenodd" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path><path fill-rule="evenodd" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"></path></svg>';
const svgCheck = '<svg role="img" aria-hidden="true" aria-labelledby="clipboardCheckmark" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><title id="clipboardCheckmark">Code snippet contents copied</title><path fill-rule="evenodd" fill="rgb(63, 185, 80)" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>';
button.className = 'clipboard-button';
button.type = 'button';
button.innerHTML = svgCopy;
button.addEventListener('click', () => {
let textToCopy = '';
let codeBlockChildren = Array.from(codeBlock.children)
codeBlockChildren.forEach(function(span) {
// lastChild is required to avoid copying line numbers
textToCopy += span.lastChild.innerText;
});
clipboard.writeText(textToCopy).then(
() => {
button.blur();
button.innerHTML = svgCheck;
setTimeout(() => (button.innerHTML = svgCopy), 2000);
},
(error) => (button.innerHTML = 'Error')
);
});
// 3. Append the button directly before the pre tag
const pre = codeBlock.parentNode;
pre.parentNode.insertBefore(button, pre);
});
};
const clipboard = (() => {
if (navigator && navigator.clipboard) {
addCopyButtons(navigator.clipboard);
}
})();
export { clipboard };

View File

@ -0,0 +1,3 @@
export { switcher } from './switchTheme';
export { clipboard } from './clipboard';
export { toggleToc } from './toc';

View File

@ -0,0 +1,46 @@
// Adapted from https://github.com/CodyHouse/dark-light-mode-switch
function switchTheme() {
let themeSwitch = document.getElementById('themeSwitch');
if (themeSwitch) {
initTheme();
themeSwitch.addEventListener('change', () => {
resetTheme();
});
function initTheme() {
let lsItem = localStorage.getItem('themeSwitch');
let darkThemeSelected = false;
if (lsItem !== null) {
darkThemeSelected = lsItem === 'dark';
} else {
darkThemeSelected = window.matchMedia('(prefers-color-scheme: dark)').matches;
}
themeSwitch.checked = darkThemeSelected;
resetTheme();
}
function resetTheme() {
if (themeSwitch.checked) {
document.body.setAttribute('data-theme', 'dark');
localStorage.setItem('themeSwitch', 'dark');
} else {
document.body.removeAttribute('data-theme');
localStorage.setItem('themeSwitch', 'light');
}
// Reset Disqus to match new color scheme
if (typeof DISQUS !== "undefined") {
DISQUS.reset({ reload: true });
}
}
}
}
const switcher = (() => {
switchTheme();
})();
export { switcher };

View File

@ -0,0 +1,12 @@
const toggleToc = (() => {
let tocToggle = document.getElementById('js-toc-toggle');
let tocContents = document.getElementById('js-toc-contents');
if (tocToggle) {
tocToggle.addEventListener('click', () => {
tocContents.classList.toggle('toc-contents--active');
});
}
})();
export { toggleToc };

View File

@ -0,0 +1,44 @@
// Show or hide nav on click of menu burger
function toggleNav() {
let mainMenu = document.getElementById('js-menu');
let navBarToggle = document.getElementById('js-navbar-toggle');
navBarToggle.addEventListener('click', () => {
mainMenu.classList.toggle('menu--active');
removeSubMenus();
});
}
// Show or hide menu items on mobile
function toggleMobileMenu() {
let menuItems = document.querySelectorAll('.menu-item');
menuItems.forEach(function(item) {
item.addEventListener('click', () => {
let subMenu = item.querySelector('.sub-menu');
if (subMenu.classList.contains('sub-menu--active')) {
subMenu.classList.remove('sub-menu--active');
} else {
removeSubMenus();
subMenu.classList.add('sub-menu--active');
}
});
});
}
// Collapse submenus
function removeSubMenus() {
let subMenus = document.querySelectorAll('.sub-menu');
subMenus.forEach(function(sub) {
if (sub.classList.contains('sub-menu--active')) {
sub.classList.remove('sub-menu--active');
}
});
}
const header = (() => {
toggleNav();
toggleMobileMenu();
})();
export { header };

View File

@ -0,0 +1,21 @@
function filterPosts() {
let selectPosts = document.getElementById('select-posts');
let entries = document.querySelectorAll('.post-entry-filter');
if (selectPosts) {
selectPosts.addEventListener('change', () => {
entries.forEach(function(entry) {
if (entry.classList.contains(`entry--${selectPosts.value}`) | selectPosts.value === 'all-posts') {
entry.style.display = 'block';
} else {
entry.style.display = 'none';
}
});
});
}
}
const home = (() => {
filterPosts();
})();
export { home };

View File

@ -0,0 +1,30 @@
// -----------------------------------------------------------------------------
// This file contains all application-wide Sass functions.
// -----------------------------------------------------------------------------
/// Native `url(..)` function wrapper
/// @param {String} $base - base URL for the asset
/// @param {String} $type - asset type folder (e.g. `fonts/`)
/// @param {String} $path - asset path
/// @return {Url}
@function asset($base, $type, $path) {
@return url($base + $type + $path);
}
/// Returns URL to an image based on its path
/// @param {String} $path - image path
/// @param {String} $base [$base-url] - base URL
/// @return {Url}
/// @require $base-url
@function image($path, $base: $base-url) {
@return asset($base, 'images/', $path);
}
/// Returns URL to a font based on its path
/// @param {String} $path - font path
/// @param {String} $base [$base-url] - base URL
/// @return {Url}
/// @require $base-url
@function font($path, $base: $base-url) {
@return asset($base, 'fonts/', $path);
}

View File

@ -0,0 +1,60 @@
// -----------------------------------------------------------------------------
// This file contains all application-wide Sass mixins.
// -----------------------------------------------------------------------------
/// Event wrapper
/// @author Harry Roberts
/// @param {Bool} $self [false] - Whether or not to include current selector
/// @link https://twitter.com/csswizardry/status/478938530342006784 Original tweet from Harry Roberts
@mixin on-event($self: false) {
@if $self {
&,
&:hover,
&:active,
&:focus,
&:focus-within {
@content;
}
} @else {
&:hover,
&:active,
&:focus,
&:focus-within {
@content;
}
}
}
/// Make a context based selector a little more friendly
/// @author Kitty Giraudel
/// @param {String} $context
@mixin when-inside($context) {
#{$context} & {
@content;
}
}
/// Mixin to manage responsive breakpoints
/// @author Kitty Giraudel
/// @param {String} $breakpoint - Breakpoint name
/// @require $breakpoints
@mixin respond-to($breakpoint) {
// If the key exists in the map
@if map-has-key($breakpoints, $breakpoint) {
// Prints a media query based on the value
@media (min-width: map-get($breakpoints, $breakpoint)) {
@content;
}
}
// If the key doesn't exist in the map
@else {
@warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. "
+ "Available breakpoints are: #{map-keys($breakpoints)}.";
}
}
@mixin font-size($step) {
font-size: var(--font-size-#{$step});
line-height: calc(8px + 2ex);
}

View File

@ -0,0 +1,92 @@
// -----------------------------------------------------------------------------
// This file contains all application-wide Sass variables.
// -----------------------------------------------------------------------------
:root, [data-theme="default"] {
--color-primary: rgb(18, 120, 175);
--color-inline-code: hsl(0, 81%, 35%);
/* color contrasts */
--color-bg: rgb(248, 248, 248);
--color-contrast-lower: hsl(0, 0%, 95%);
--color-contrast-low: hsl(240, 1%, 83%);
--color-contrast-medium-low: hsl(240, 1%, 65%);
--color-contrast-medium: hsl(240, 1%, 48%);
--color-contrast-medium-high: hsl(240, 2%, 34%);
--color-contrast-high: hsl(240, 4%, 20%);
--color-contrast-higher: black;
--color-text: var(--color-contrast-high);
--font-size-sm: clamp(0.8rem, 0.17vw + 0.76rem, 0.89rem);
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
--font-size-md: clamp(1.25rem, 0.61vw + 1.1rem, 1.58rem);
--font-size-lg: clamp(1.56rem, 1vw + 1.31rem, 2.11rem);
--font-size-xl: clamp(1.95rem, 1.56vw + 1.56rem, 2.81rem);
--font-size-xxl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
--font-size-xxxl: clamp(3.05rem, 3.54vw + 2.17rem, 5rem);
}
[data-theme] {
background-color: var(--color-bg);
color: var(--color-contrast-high);
}
[data-theme="dark"] {
--color-primary: rgb(86, 184, 237);
--color-inline-code: hsl(0, 81%, 70%);
/* color contrasts */
--color-bg: rgb(18, 18, 18);
--color-contrast-lower: hsl(240, 6%, 15%);
--color-contrast-low: hsl(252, 4%, 25%);
--color-contrast-medium-low: hsl(240, 2%, 34%);
--color-contrast-medium: hsl(240, 1%, 57%);
--color-contrast-medium-high: hsl(240, 1%, 65%);
--color-contrast-high: hsl(0, 0%, 89%);
--color-contrast-higher: white;
--color-text: var(--color-contrast-high);
}
// Fonts
/// Regular font family
/// @type List
$text-font-stack: 'Roboto', 'Helvetica Neue Light', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
/// Code (monospace) font family
/// @type List
$code-font-stack: 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Monaco', monospace;
// Containers
/// Container's maximum width
/// @type Length
$max-width: 1180px;
// Responsiveness
/// Breakpoints map
/// @prop {String} keys - Keys are identifiers mapped to a given length
/// @prop {Map} values - Values are actual breakpoints expressed in pixels
$breakpoints: (
'x-small': 320px,
'small': 576px,
'medium': 768px,
'm-large': 900px,
'large': 1024px,
'x-large': 1200px,
);
// Assets
/// Relative or absolute URL where all assets are served from
/// @type String
/// @example scss - When using a CDN
/// $base-url: 'https://cdn.example.com/assets/';
$base-url: '/assets/';

View File

@ -0,0 +1,178 @@
// -----------------------------------------------------------------------------
// This file contains very basic styles.
// -----------------------------------------------------------------------------
/**
* Set up a decent box model on the root element
*/
html {
box-sizing: border-box;
}
/**
* Make all elements from the DOM inherit from the parent box-sizing
* Since `*` has a specificity of 0, it does not override the `html` value
* making all elements inheriting from the root box-sizing value
* See: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/
*/
*,
*::before,
*::after {
box-sizing: inherit;
}
html, body {
height: 100%;
}
body {
/* Flex properties on body/main/footer are for floating footer
to bottom of page if main content doesn't fill viewport vertically */
display: flex;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
footer {
flex-shrink: 0;
}
a {
color: var(--color-primary);
text-decoration: none;
@include on-event {
color: var(--color-text);
text-decoration: underline;
}
}
h1 {
@include font-size('xl');
}
h2 {
@include font-size('lg');
}
h3 {
@include font-size('md');
}
h4 {
@include font-size('base');
}
h5 {
@include font-size('sm');
}
h6 {
@include font-size('sm');
}
table {
border-collapse: collapse;
display: block;
overflow-x: auto;
}
td, th {
border: 1px solid var(--color-contrast-medium-low);
padding: 10px 20px;
font-size: 0.9rem;
line-height: 1.4rem;
}
th {
border: 1px solid var(--color-contrast-medium);
background-color: var(--color-contrast-medium-low);
color: var(--color-contrast-high);
font-size: 1rem;
}
td {
text-align: center;
}
tr:nth-child(even) td {
background-color: var(--color-contrast-lower);
color: var(--color-contrast-high);
}
tr:nth-child(odd) td {
background-color: var(--color-contrast-low);
color: var(--color-contrast-high);
}
blockquote {
background: var(--color-contrast-lower);
border-left: 10px solid var(--color-contrast-low);
margin: 1.5em 10px;
padding: 0.7em 10px;
quotes: "\201C" "\201D";
p {
display: inline;
}
&::before {
color: var(--color-contrast-low);
content: open-quote;
font-size: 4em;
line-height: 0.1em;
margin-right: 0.25em;
vertical-align: -0.4em;
}
}
pre {
font-size: 1rem;
line-height: 1.6rem;
overflow-x: auto;
}
code {
overflow-x: scroll;
}
pre:not([style]) {
// If no highlighting is applied already
background-color:#272822;
color:#f8f8f2;
padding: 20px;
}
p > code, li > code {
background-color: var(--color-contrast-lower);
font-size: 1rem;
color: var(--color-inline-code);
padding: 2px 5px;
border-radius: 5px;
}
form {
display: flex;
flex-wrap: wrap;
row-gap: 10px;
input, textarea {
border: 1px solid var(--color-contrast-medium-low);
padding: 10px 12px;
font-size: 1rem;
background-color: var(--color-contrast-lower);
color: var(--color-contrast-high);
@include respond-to('small') {
padding: 15px 12px;
min-width: 250px;
}
}
button {
cursor: pointer;
}
}

View File

@ -0,0 +1,111 @@
// -----------------------------------------------------------------------------
// This file contains all @font-face declarations, if any.
// -----------------------------------------------------------------------------
@font-face {
font-family: 'Roboto';
src: local('Roboto Thin'),
url('/fonts/Roboto/Roboto-Thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Thin Italic'),
url('/fonts/Roboto/Roboto-ThinItalic.ttf') format('truetype');
font-weight: 100;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Light'),
url('/fonts/Roboto/Roboto-Light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Light Italic'),
url('/fonts/Roboto/Roboto-LightItalic.ttf') format('truetype');
font-weight: 300;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Regular'),
url('/fonts/Roboto/Roboto-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Italic'),
url('/fonts/Roboto/Roboto-Italic.ttf') format('truetype');
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Medium'),
url('/fonts/Roboto/Roboto-Medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Italic'),
url('/fonts/Roboto/Roboto-MediumItalic.ttf') format('truetype');
font-weight: 500;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Bold'),
url('/fonts/Roboto/Roboto-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Bold Italic'),
url('/fonts/Roboto/Roboto-BoldItalic.ttf') format('truetype');
font-weight: 700;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Black'),
url('/fonts/Roboto/Roboto-Black.ttf') format('truetype');
font-weight: 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: local('Roboto Black Italic'),
url('/fonts/Roboto/Roboto-BlackItalic.ttf') format('truetype');
font-weight: 900;
font-style: italic;
font-display: swap;
}

View File

@ -0,0 +1,69 @@
// -----------------------------------------------------------------------------
// This file contains CSS helper classes.
// -----------------------------------------------------------------------------
/**
* Clear inner floats
*/
.clearfix::after {
clear: both;
content: '';
display: table;
}
/**
* Main content containers
* 1. Make the container full-width with a maximum width
* 2. Center it in the viewport
* 3. Leave some space on the edges, especially valuable on small screens
*/
.container {
max-width: $max-width; /* 1 */
margin-left: auto; /* 2 */
margin-right: auto; /* 2 */
padding-left: 16px; /* 3 */
padding-right: 16px; /* 3 */
width: 100%; /* 1 */
@include respond-to('small') {
padding-left: 20px; /* 3 */
padding-right: 20px; /* 3 */
}
&--sm {
@extend .container;
max-width: 768px;
}
}
/**
* Hide text while making it readable for screen readers
* 1. Needed in WebKit-based browsers because of an implementation bug;
* See: https://code.google.com/p/chromium/issues/detail?id=457146
*/
.hide-text {
overflow: hidden;
padding: 0; /* 1 */
text-indent: 101%;
white-space: nowrap;
}
/**
* Hide element while making it readable for screen readers
* Shamelessly borrowed from HTML5Boilerplate:
* https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css#L119-L133
*/
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.section {
padding: 50px 0;
}

View File

@ -0,0 +1,31 @@
// -----------------------------------------------------------------------------
// This file contains typography styles.
// -----------------------------------------------------------------------------
/**
* Basic typography style for copy text
*/
body {
color: var(--color-text);
font: normal 125% / 1.4 $text-font-stack;
}
.summary-text {
font-weight: 300;
@include font-size('base');
color: var(--color-contrast-medium-high);
}
.meta-text {
color: var(--color-contrast-medium);
@include font-size('sm');
font-weight: 400;
display: flex;
flex-wrap: wrap;
gap: 6px 15px;
}
.fallback-text {
color: var(--color-contrast-medium);
@include font-size('md');
}

View File

@ -0,0 +1,40 @@
.btn-group {
display: flex;
column-gap: 15px;
margin-top: 30px;
}
%btn {
border: 1px solid var(--color-primary);
padding: 0.4rem 0.7rem;
display: inline-block;
@include font-size('base');
@include respond-to('small') {
padding: 0.5rem 0.8rem;
}
@include on-event() {
text-decoration: none;
}
}
.btn-primary {
@extend %btn;
background-color: var(--color-primary);
color: var(--color-contrast-lower);
&:hover {
background: transparent;
color: var(--color-primary);
}
}
.btn-secondary {
@extend %btn;
&:hover {
background-color: var(--color-primary);
color: var(--color-contrast-lower);
}
}

View File

@ -0,0 +1,44 @@
// Adapted from the following tutorials:
// https://logfetch.com/hugo-add-copy-to-clipboard-button/
.clipboard-button {
position: absolute;
right: 0;
padding: 2px 7px 5px 7px;
margin: 5px;
color: #767676;
border-color: #767676;
background-color: #ededed;
border: 1px solid;
border-radius: 6px;
z-index: 1;
opacity: 0;
transition: 0.1s;
}
.clipboard-button > svg {
fill: #767676;
}
.clipboard-button:hover {
cursor: pointer;
border-color: #696969;
background-color: #e0e0e0;
}
.clipboard-button:hover > svg {
fill: #696969;
}
.clipboard-button:focus {
outline: 0;
}
.highlight {
position: relative;
}
.highlight:hover > .clipboard-button {
opacity: 1;
transition: 0.2s;
}

View File

@ -0,0 +1,14 @@
.code-language {
position: relative;
padding: 6px 15px;
border-radius: 5px;
background-color: #272822;
color: #7f7f7f;
z-index: 1000;
top: 25px;
@include font-size('base');
}
.highlight > pre {
padding: 20px;
}

View File

@ -0,0 +1,9 @@
.draft::after {
content: 'Draft';
color: rgb(201, 8, 8);
border: 1px solid rgb(201, 8, 8);
border-radius: 5px;
@include font-size('sm');
padding: 2px 5px;
font-weight: 300;
}

View File

@ -0,0 +1,19 @@
.markdown {
@include font-size('base');
p > img, figure > img {
max-width: 100%;
height: auto;
}
figure {
margin-left: 0;
margin-right: 0;
}
figcaption {
@include font-size('sm');
color: var(--color-contrast-medium);
font-weight: 300;
}
}

View File

@ -0,0 +1,16 @@
.newsletter-header {
color: var(--color-contrast-high);
margin-bottom: 0;
}
.newsletter-desc {
color: var(--color-contrast-medium-high);
@include font-size('base');
}
#newsletter-form {
input {
border-right: none;
}
}

View File

@ -0,0 +1,26 @@
.page-header {
background-color: var(--color-contrast-lower);
padding: 30px;
margin: 0 0 50px 0;
border-radius: 20px;
}
.page-header--c {
@extend .page-header;
text-align: center;
}
.page-header-title {
margin: 0;
color: var(--color-contrast-high);
font-weight: 500;
@include font-size('lg');
}
.page-header-desc {
margin: 0;
margin-top: 15px;
color: var(--color-contrast-medium);
font-weight: 400;
@include font-size('base');
}

View File

@ -0,0 +1,37 @@
.pagination {
list-style-type: none;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 15px 10px;
margin-top: 50px;
padding: 0;
@include font-size('base');
}
.page-link {
color: var(--color-contrast-medium-high);
padding: 8px 15px;
background-color: var(--color-contrast-lower);
&:hover {
color: var(--color-primary);
}
@include on-event {
text-decoration: none;
}
}
.page-item {
&.disabled > a {
color: var(--color-contrast-low);
cursor:unset;
}
&.active > a {
background-color: var(--color-primary);
color: var(--color-contrast-lower);
}
}

View File

@ -0,0 +1,29 @@
.social-links {
width: 100%;
opacity: 0.9;
}
.social-icons {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 15px 40px;
list-style: none;
padding: 0;
margin: 0;
&--share {
@extend .social-icons;
justify-content: flex-start;
gap: 10px 15px;
li {
border: 1px solid var(--color-contrast-medium-low);
border-radius: 100px;
padding: 12px;
display: flex;
justify-content: center;
align-items: center;
}
}
}

View File

@ -0,0 +1,105 @@
:root {
/* style */
--switch-width: 48px;
--switch-height: 24px;
--switch-padding: 3px;
/* animation */
--switch-animation-duration: 0.2s;
}
.switch {
display: flex;
flex-shrink: 0;
align-items: center;
width: 48px;
width: var(--switch-width);
height: 24px;
height: var(--switch-height);
border-radius: 50em;
padding: 3px 0;
padding: var(--switch-padding) 0;
position: absolute;
top: 65px;
right: 20px;
@include respond-to('m-large') {
position:relative;
top: unset;
right: unset;
}
}
.switch-input, .switch-label {
position: absolute;
left: 0;
top: 0;
}
.switch-input {
margin: 0;
padding: 0;
opacity: 0;
height: 0;
width: 0;
pointer-events: none;
}
.switch-input:checked + .switch-label {
background-color: hsl(228, 74%, 61%);
background-color: var(--color-primary);
}
.switch-input:checked + .switch-label + .switch-marker {
left: calc(100% - 29px);
left: calc(100% - var(--switch-height) + var(--switch-padding));
}
.switch-input:focus + .switch-label,
.switch-input:active + .switch-label {
--color-shadow: hsla(228, 74%, 61%, 0.2);
box-shadow: undefined;
box-shadow: 0 0 0 3px var(--color-shadow);
}
.switch-input:focus + .switch-label,
.switch-input:active + .switch-label {
box-shadow: 0 0 0 3px hsla(228, 74%, 61%, 0.2);
box-shadow: 0 0 0 3px var(--color-shadow);
}
.switch-label {
width: 100%;
height: 100%;
color: transparent;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: hsl(240, 1%, 83%);
background-color: var(--color-contrast-low);
border-radius: inherit;
z-index: 1;
transition: background 0.2s;
transition: background var(--switch-animation-duration);
}
.switch-marker {
position: relative;
background-color: hsl(0, 0%, 100%);
background-color: var(--color-contrast-high);
width: 20px;
width: calc(var(--switch-height) - var(--switch-padding)*2);
height: 20px;
height: calc(var(--switch-height) - var(--switch-padding)*2);
border-radius: 50%;
z-index: 2;
pointer-events: none;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
left: 3px;
left: var(--switch-padding);
transition: left 0.2s;
transition: left var(--switch-animation-duration);
will-change: left;
}

View File

@ -0,0 +1,52 @@
.toc {
@include respond-to('x-large') {
position: sticky;
top: 2rem;
align-self: start;
order: 2;
display: flex;
flex-direction: column;
align-items: center;
height: 90vh;
overflow-y: scroll;
}
}
.toc-header, .toc-drop-icon {
@include font-size('sm');
font-weight: 500;
margin: 0;
text-align: center;
}
.toc-contents {
display: none;
@include font-size('sm');
&--active {
display: block;
}
@include respond-to('x-large') {
display: block;
&--active {
display: none;
}
}
}
#js-toc-toggle {
display: inline-flex;
align-items: center;
column-gap: 10px;
padding: 10px 20px;
background-color: var(--color-contrast-lower);
border-radius: 20px;
&:hover {
cursor: pointer;
color: var(--color-primary);
}
}

View File

@ -0,0 +1,18 @@
// -----------------------------------------------------------------------------
// This file contains all styles related to the footer of the site/application.
// -----------------------------------------------------------------------------
.footer {
background-color: var(--color-contrast-lower);
}
.footer-socials {
max-width: 300px;
margin: 0 auto;
}
.footer-copyright {
text-align: center;
@include font-size('base');
color: var(--color-contrast-medium-high);
}

View File

@ -0,0 +1,128 @@
// -----------------------------------------------------------------------------
// This file contains all styles related to the header of the site.
// -----------------------------------------------------------------------------
.main-nav {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 15px 0;
row-gap: 20px;
.nav-toggle {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
@include respond-to('m-large') {
flex-direction: row; /* Horizontal nav on desktop */
align-items: center;
padding: 0;
.nav-toggle {
display: none; /* Hide nav toggle on desktop */
}
}
}
.logo {
@include font-size('md');
font-weight: 700;
text-decoration: none;
width: fit-content;
&:hover {
text-decoration: none;
}
}
.menu-link {
color: var(--color-text);
&:hover {
color: var(--color-contrast-lower);
}
}
.menu {
display: none;
flex-direction: column;
margin: 0;
padding: 0;
border-bottom: 1px solid var(--color-contrast-low);
border-top: 1px solid var(--color-contrast-low);
&--active {
display: flex; /* Display mobile menu on click */
}
.menu-item {
display: block;
list-style-type: none;
}
.menu-item--align {
@extend .menu-item;
align-self: center;
margin-left: 20px;
}
.menu-link {
display: flex;
font-size: 1rem;
font-weight: 500;
text-align: center;
text-decoration: none;
cursor: pointer;
padding: 1.4rem 1rem;
&:hover {
background-color: var(--color-primary);
color: var(--color-contrast-lower);
}
}
.drop-icon {
margin-left: 10px;
}
@include respond-to('m-large') {
display: flex;
flex-direction: row;
border: none;
.menu-item:hover {
.sub-menu {
background-color: var(--color-contrast-lower);
padding-left: 0;
display: block;
z-index: 1;
}
}
}
}
.sub-menu {
display: none;
&--active {
display: block;
z-index: 1;
}
.menu-link {
font-weight: initial;
}
@include respond-to('m-large') {
display: none;
position: absolute;
box-shadow: rgba(9, 30, 66, 0.25) 0px 4px 8px -2px, rgba(9, 30, 66, 0.08) 0px 0px 0px 1px;
&--active {
display: none;
}
}
}

View File

@ -0,0 +1,47 @@
@charset "UTF-8";
// 1. Configuration and helpers
@import
'abstracts/variables',
'abstracts/functions',
'abstracts/mixins';
// 2. Vendors
@import
'vendors/normalize';
// 3. Base stuff
@import
'base/base',
'base/fonts',
'base/typography',
'base/helpers';
// 4. Layout-related sections
@import
'layout/header',
'layout/footer';
// 5. Components
@import
'components/switch-theme',
'components/socials',
'components/buttons',
'components/newsletter',
'components/pagination',
'components/draft-label',
'components/clipboard',
'components/code-highlight',
'components/markdown',
'components/toc',
'components/page-header';
// 6. Page-specific styles
@import
'pages/home',
'pages/contact',
'pages/single',
'pages/terms',
'pages/errors',
'pages/post-list',
'pages/project-list';

View File

@ -0,0 +1,10 @@
#contact-form {
flex-direction: column;
row-gap: 15px;
max-width: 500px;
margin-top: 30px;
button {
align-self: flex-start;
}
}

View File

@ -0,0 +1,11 @@
.error-404 {
margin-top: 50px;
h1 {
margin: 0;
}
p {
margin-top: 10px;
}
}

View File

@ -0,0 +1,104 @@
// -----------------------------------------------------------------------------
// This file contains styles that are specific to the home page.
// -----------------------------------------------------------------------------
.hero {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 3rem 5vw;
@include respond-to('medium') {
flex-wrap: nowrap;
}
@include respond-to('m-large') {
padding: 80px 0;
}
}
.hero-info {
@include respond-to('medium') {
flex: 0 1 65%;
}
.hero-title {
font-weight: 900;
margin-top: 0;
@include font-size('xl');
}
.hero-subtitle {
color: var(--color-contrast-medium-high);
@include font-size('base');
}
}
.hero-owner {
display: flex;
flex-direction: column;
align-items: center;
row-gap: 20px;
@include respond-to('medium') {
flex: 0 1 35%;
}
.hero-avatar {
max-width: 300px;
width: 100%;
height: auto;
border-radius: 20px;
@include respond-to('medium') {
max-width: 100%;
width: unset;
}
}
}
.home-section-title {
&::after {
background-color: var(--color-contrast-medium);
content: "";
display: block;
height: 2px;
position: relative;
width: 80px;
top: 8px;
}
}
.home-section-posts-title {
@extend .home-section-title;
margin: 0;
}
.home-title-dropdown {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 30px;
margin-bottom: 50px;
}
#select-posts {
@include font-size('sm');
padding: 0.4rem;
border: 1px solid var(--color-contrast-medium-low);
border-radius: 5px;
color: var(--color-contrast-high);
background-color: var(--color-contrast-lower);
}
.see-more {
margin-top: 30px;
}
.see-more-projects {
color: var(--color-contrast-medium);
font-weight: 300;
font-size: var(--font-size-base);
}

View File

@ -0,0 +1,47 @@
.post-entry {
margin: 20px 0;
max-width: 750px;
}
.post-entry-divider {
background-color: var(--color-contrast-low);
content: "";
display: block;
height: 1px;
position: relative;
max-width: 750px;
}
.post-list-title {
font-weight: 400;
margin: 0;
@include font-size('md');
a {
text-decoration: none;
color: var(--color-contrast-high);
&:hover {
color: var(--color-primary);
}
}
}
.post-list-summary {
@extend .summary-text;
margin: 10px 0 0 0;
}
.post-list-meta {
@extend .meta-text;
margin-top: 10px;
}
.post-list-dates {
font-weight: 400;
}
.post-list-categories {
display: inline-flex;
column-gap: 10px;
}

View File

@ -0,0 +1,81 @@
.project-list {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 2rem 1rem;
& > * {
flex: 1 1 350px;
}
}
.project-entry {
border: 1px solid var(--color-contrast-low);
border-radius: 10px;
box-shadow: 0 0 20px -10px var(--color-contrast-low), 0 0 20px -10px var(--color-contrast-low);
max-width: 750px;
}
.project-entry-img {
position: relative;
border-radius: 10px 10px 0 0;
min-height: 1rem;
img {
width: 100%;
height: 20vh;
object-fit: cover;
border-radius: 10px 10px 0 0;
border-bottom: 1px solid var(--color-contrast-low);
// Image overlay
z-index: -1;
position: relative;
filter: grayscale(50%);
}
}
.project-entry-type {
position: absolute;
top: 0;
right: 0;
padding: 0.2rem 0.4rem;
background-color: var(--color-bg);
border-left: 1px solid var(--color-contrast-low);
border-bottom: 1px solid var(--color-contrast-low);
border-top-right-radius: 10px;
a {
@include font-size('sm');
}
}
.project-entry-info {
padding: 1.2rem;
@include respond-to('small') {
padding: 1.5rem;
}
}
.project-entry-title {
margin: 0;
font-weight: 400;
a {
color: var(--color-contrast-high);
&:hover {
text-decoration: none;
color: var(--color-primary);
}
}
}
.project-list-summary {
@extend .summary-text;
}
.project-list-meta {
@extend .meta-text;
margin-top: 10px;
}

View File

@ -0,0 +1,80 @@
.single-feature-img {
display: flex;
img {
margin: 30px 0;
max-width: 100%;
height: auto;
filter: grayscale(50%);
}
}
.single-terms {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-start;
margin-top: 20px;
}
.single-container {
@extend .container;
max-width: 900px;
}
.single-container-post {
@extend .container;
max-width: 900px;
@include respond-to('x-large') {
max-width: $max-width;
display: grid;
grid-template-columns: 1fr 15em;
gap: 1rem;
}
}
.single-post-contents {
overflow: auto;
}
.series {
@include font-size('base');
margin: 2rem 0;
}
.series-this-post {
color: var(--color-primary);
border: 1px solid var(--color-primary);
border-radius: 5px;
padding: 0.3rem;
@include font-size('sm');
font-weight: 500;
margin-left: 10px;
}
.single-next-previous {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: space-between;
align-items: baseline;
& > * {
background-color: transparent;
border: 1px solid var(--color-contrast-medium-low);
border-radius: 12px;
padding: 10px;
@include font-size('base');
max-width: 300px;
&:hover {
text-decoration: none;
border: 1px solid var(--color-contrast-high);
}
}
}
.related-posts {
@include font-size('base');
}

View File

@ -0,0 +1,28 @@
.terms {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px 15px;
}
.term {
border: 1px solid var(--color-primary);
border-radius: 20px;
@include font-size('sm');
padding: 0.4rem 0.6rem;
&:hover {
text-decoration: none;
border: 1px solid var(--color-contrast-high);
color: var(--color-contrast-high);
}
@include respond-to('small') {
padding: 0.5rem 0.7rem;
}
}
.term-count {
color: var(--color-contrast-high);
margin-left: 2px;
}

View File

@ -0,0 +1,349 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

View File

@ -0,0 +1,4 @@
[module]
[module.hugoVersion]
extended = true
min = "0.115.2"

View File

@ -0,0 +1,3 @@
module github.com/wjh18/hugo-liftoff/v3
go 1.20

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View File

@ -0,0 +1,9 @@
{{ define "main" }}
<article class="error-404 container">
<div>
<h1>Page Not Found!</h1>
<p>The page you requested could not be found.</p>
<a class="btn-secondary" href="{{ "/" | relURL }}">&lsaquo; Return Home</a>
</div>
</article>
{{ end }}

View File

@ -0,0 +1,6 @@
{{ $lang := .Attributes.lang | default .Type }}
{{ if transform.CanHighlight $lang }}
<span class="code-language">{{ $lang }}</span>{{ highlight .Inner $lang }}
{{ else }}
<pre><code>{{ .Inner }}</code></pre>
{{ end }}

View File

@ -0,0 +1,10 @@
<h{{ .Level }} id="{{ .Anchor | safeURL }}">
{{- .Text | safeHTML -}}
<a href="#{{ .Anchor | safeURL }}">
<svg role="img" aria-labelledby="{{ .Anchor | safeURL }}-IconTitle" fill="var(--color-primary)" height="22" viewBox="0 0 24 24" width="22" xmlns="http://www.w3.org/2000/svg">
<title id="{{ .Anchor | safeURL }}-IconTitle">Link to this heading</title>
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76.0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71.0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71.0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76.0 5-2.24 5-5s-2.24-5-5-5z"></path>
</svg>
</a>
</h{{ .Level }}>

View File

@ -0,0 +1,50 @@
<!-- get image URL from Markdown tag -->
{{- $src := (.Destination | safeURL) -}}
<!-- split # fragment (used for CSS classes) and keep clean URL -->
{{- $fragments := (split $src "#") -}}
{{- $src = index ($fragments) 0 -}}
<!-- resize if wider than .Scratch.Get "imgwidth" -->
{{- $imgwidth := .Page.Scratch.Get "imgwidth" | default 800 -}}
<!-- get actual filename -->
{{- $src = path.Base $src -}}
<!-- check if it exists as a page resource -->
{{- with (.Page.Resources.ByType "image").GetMatch (printf "**/%s" $src) -}}
{{ $resized := . }}
{{ if (gt .Width $imgwidth) }}
{{ if hugo.IsExtended }}
{{- $resized = .Resize (print $imgwidth "x webp") -}}
{{ else }}
{{- $resized = .Resize (print $imgwidth "x") -}}
{{ end }}
{{ end }}
<!-- if a JPEG (certain to be opaque) generate a low resolution placeholder to use as background -->
{{ $placeholder := "" }}
{{- if or (eq .MediaType.SubType "jpg") (eq .MediaType.SubType "jpeg") }}
{{ $placeholder = .Resize "48x q20 jpg Gaussian" }}
{{ end -}}
<!-- if a GIF file, then revert to original to avoid resizing animations; WebP animations don't work -->
{{- if (eq .MediaType.SubType "gif") }}
{{ $resized = . }}
{{ end -}}
<img src="{{ $resized.RelPermalink }}"
width="{{ $resized.Width }}"
height="{{ $resized.Height }}"
{{ with $placeholder }}style="
background-image: url('data:image/jpg;base64,{{ .Content | base64Encode }}');
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;"{{ end }}
alt="{{ $.Text }}" {{ with $.Title }} title="{{ . }}" {{ end }}
{{ with index ($fragments ) 1 }}class="{{ . }}" {{ else }}class="single-post-image" {{ end }}
loading="lazy"
decoding="async"
>
<!-- or otherwise simply load the URL -->
{{- else -}}
<img src="{{ .Destination | safeURL }}"
alt="{{ .Text }}" {{ with .Title }} title="{{ . }}" {{ end }}
{{ with index ($fragments ) 1 }}class="{{ . }}" {{ else }}class="single-post-image" {{ end }}
loading="lazy"
decoding="async"
>
{{- end -}}

View File

@ -0,0 +1 @@
<a href="{{ .Destination | safeURL }}"{{ with .Title }} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener"{{ end }}>{{ .Text | safeHTML }}</a>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode | default "en-US" }}">
{{- partial "head/head.html" . -}}
<body>
{{ if and (not .Site.IsServer) (.Site.Params.gtm_id) }}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ .Site.Params.gtm_id }}" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{{ end }}
{{- partial "header/header.html" . -}}
<main class="section">
{{- block "main" . }}{{- end }}
</main>
{{- partial "footer/footer.html" . -}}
{{- partial "footer/scripts.html" . -}}
</body>
</html>

View File

@ -0,0 +1,31 @@
{{ define "main" -}}
<div class="container">
<section class="page-header">
<h1 class="page-header-title">{{ .Title }}</h1>
<p class="page-header-desc">
{{- with .Params.Summary -}}
{{- . -}}
{{- else -}}
{{- with .Description -}}
{{- . -}}
{{- else -}}
{{- if eq .Data.Plural "series" -}}
Posts in the {{ .Title }} series.
{{- else -}}
Posts about {{ .Title }}.
{{- end -}}
{{- end -}}
{{- end -}}
</p>
</section>
<section>
{{ range (.Paginate .RegularPagesRecursive).Pages }}
{{ partial "posts/post-entry.html" . }}
<div class="post-entry-divider"></div>
{{ else }}
{{ partial "general/fallback-text.html" . }}
{{ end }}
{{ template "_internal/pagination.html" . }}
</section>
</div>
{{ end }}

View File

@ -0,0 +1,27 @@
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ with .Site.Title }}{{ . }}{{ else }}{{ .Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
<language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" -}}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end -}}
{{ range where (where .Site.Pages ".Section" "posts") "Kind" "page" }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid>
<description>{{ with .Params.Summary }}{{ . | html }}{{ else }}{{ .Description | html }}{{ end }}</description>
</item>
{{ end }}
</channel>
</rss>

View File

@ -0,0 +1,12 @@
{{ define "main" }}
<div class="container">
<section class="page-header--c">
<h1 class="page-header-title">{{ .Title }}</h1>
</section>
</div>
<section class="generic-single">
<div class="single-container markdown">
{{ .Content }}
</div>
</section>
{{ end }}

View File

@ -0,0 +1,24 @@
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
{{ range .Data.Pages }}
{{- if .Permalink -}}
<url>
<loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
<lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
<changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
<priority>{{ .Sitemap.Priority }}</priority>{{ end }}{{ if .IsTranslated }}{{ range .Translations }}
<xhtml:link
rel="alternate"
hreflang="{{ .Language.Lang }}"
href="{{ .Permalink }}"
/>{{ end }}
<xhtml:link
rel="alternate"
hreflang="{{ .Language.Lang }}"
href="{{ .Permalink }}"
/>{{ end }}
</url>
{{- end -}}
{{ end }}
</urlset>

View File

@ -0,0 +1,16 @@
{{ define "main" }}
<div class="container">
<section class="page-header--c">
<h1 class="page-header-title">{{ .Title }}</h1>
</section>
<div class="terms">
{{ with .Data.Terms.ByCount }}
{{ range . }}
<a class="term" href="{{ .Page.Permalink }}">{{ .Page.Title }} <sup class="term-count">{{ .Count }}</sup></a>
{{ end }}
{{ else }}
{{ partial "general/fallback-text.html" . }}
{{ end }}
</div>
</div>
{{ end }}

View File

@ -0,0 +1,15 @@
{{ define "main" }}
<div class="container">
<section class="page-header--c">
<h1 class="page-header-title">{{ .Title }}</h1>
</section>
</div>
<div class="about">
{{ if eq .Site.Params.about_page_socials true }}
<div class="container--sm">{{ partial "general/social-links.html" . }}</div>
{{ end }}
<article class="single-container markdown">
{{ .Content }}
</article>
</div>
{{ end }}

View File

@ -0,0 +1,16 @@
{{ define "main" }}
<div class="container">
<section class="page-header--c">
<h1 class="page-header-title">{{ .Title }}</h1>
</section>
</div>
<div class="contact">
<div class="single-container markdown">{{ .Content }}</div>
<form id="contact-form" class="single-container" name="contact" method="POST" {{ with .Site.Params.netlify_forms }}data-netlify="{{ . }}"{{ end }}>
<input type="text" class="contact-form--input" name="name" placeholder="Your name..." title="Enter your name" required>
<input type="email" class="contact-form--input" name="email" placeholder="Your best email..." title="Enter your best email" required>
<textarea class="contact-form--textarea" name="message" placeholder="Your message..." title="Enter your message" required></textarea>
<button type="submit" id="contact-form--submit" class="btn-primary">Send</button>
</form>
</div>
{{ end }}

View File

@ -0,0 +1,5 @@
/*
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: origin-when-cross-origin

View File

@ -0,0 +1,52 @@
{{ define "main" }}
<div class="container">
<section class="section hero">
<div class="hero-info">
<h1 class="hero-title">{{ .Title }}</h1>
{{ with .Params.subtitle }}
<p class="hero-subtitle">{{. | markdownify}}</p>
{{ end }}
<div class="btn-group">
{{ $p_cta := "" }}
{{ with .Params.primary_cta_page }}
{{ $p_cta = . }}
{{ end }}
{{ $s_cta := "" }}
{{ with .Params.secondary_cta_page }}
{{ $s_cta = . }}
{{ end }}
<a class="btn-primary"
href="{{ with $p_cta }}{{ . | relURL }}{{ else }}{{ "about" | relURL }}{{ end }}">
{{ if $p_cta }}{{ with .Site.GetPage $p_cta }}{{ .LinkTitle }}{{ end }}{{ else }}About{{ end }}
</a>
<a class="btn-secondary"
href="{{ with $s_cta }}{{ . | relURL }}{{ else }}{{ "projects" | relURL }}{{ end }}">
{{ if $s_cta }}{{ with .Site.GetPage $s_cta }}{{ .LinkTitle }}{{ end }}{{ else }}Projects{{ end }}
</a>
</div>
</div>
<div class="hero-owner">
{{ $avatar := .Site.Params.avatar }}
{{ $default_image := "images/default.png" }}
{{ $image := "" }}
{{ with $avatar }}
{{ $image = resources.Get $avatar }}
{{ else }}
{{ $image = resources.Get $default_image }}
{{ end }}
{{ if eq .Site.Params.grayscale_avatar true }}
{{ $image = $image | images.Filter (images.Grayscale) }}
{{ end }}
<img class="hero-avatar"
src="{{ $image.RelPermalink }}"
width="{{- $image.Width -}}" height="{{- $image.Height -}}"
alt="Headshot or avatar belonging to the website owner"/>
{{ if eq .Site.Params.home_hero_socials true }}
{{ partial "general/social-links.html" . }}
{{ end }}
</div>
</section>
{{/* Shortcodes from page content */}}
{{ .Content }}
</div>
{{ end }}

View File

@ -0,0 +1,7 @@
# redirects for Netlify - https://www.netlify.com/docs/redirects/
{{- range $p := .Site.Pages -}}
{{- range .Aliases }}
{{ . }} {{ $p.RelPermalink -}}
{{- end }}
{{- end -}}
# Add custom redirects here

View File

@ -0,0 +1,18 @@
<footer>
{{ if (or (.Param "newsletter") (and (.Site.Params.global_newsletter) (not (isset .Params "newsletter")))) }}
{{ partial "footer/newsletter.html" . }}
{{ end }}
<div class="section footer">
<p class="footer-copyright">&copy; {{ now.Format "2006" }} &middot;
<a href="{{ .Site.BaseURL }}">{{ .Site.Title }}</a>
{{ with .Site.Params.footer_text | markdownify }}
<span>&middot; {{ . }}</span>
{{ end }}
</p>
{{ if eq .Site.Params.footer_socials true }}
<div class="footer-socials">
{{ partial "general/social-links.html" . }}
</div>
{{ end }}
</div>
</footer>

View File

@ -0,0 +1,29 @@
<!-- Reusable template for a newsletter opt-in -->
<section class="section">
<div class="container--sm">
<h2 class="newsletter-header">
{{- with .Site.Params.newsletter_header -}}
{{- . -}}
{{- else -}}
Subscribe for Post Updates
{{- end -}}
</h2>
<p class="newsletter-desc">
{{- with .Site.Params.newsletter_description -}}
{{- . -}}
{{- else -}}
Enter your email below to get notified of new posts.
{{- end -}}
</p>
<form id="newsletter-form" name="newsletter" method="POST" {{ with .Site.Params.netlify_forms }}data-netlify="{{ . }}"{{ end }}>
<input type="email" id="newsletter-form--input" name="email" placeholder="Your best email..." title="Enter your best email" required>
<button type="submit" id="newsletter-form--submit" class="btn-primary">
{{- with .Site.Params.newsletter_submit -}}
{{- . -}}
{{- else -}}
Subscribe
{{- end -}}
</button>
</form>
</div>
</section>

View File

@ -0,0 +1,34 @@
{{ $opts := "" }}
{{ if hugo.IsProduction }}
{{ $opts = dict "targetPath" "main.js" }}
{{ else }}
{{ $opts = dict "targetPath" "main.js" "sourceMap" "inline" }}
{{ end }}
{{ $script := resources.Get "js/app.js" | js.Build $opts }}
{{ $custom := "" }}
{{ with .Site.Params.custom_js }}
{{ $custom = resources.Get . }}
{{/* Only concatenate in production to allow source maps */}}
{{ if hugo.IsProduction }}
{{ $bundle := slice $script $custom | resources.Concat "main.js" }}
{{ $script = $bundle }}
{{ else }}
<script src="{{ $custom.Permalink }}"></script>
{{ end }}
{{ end }}
{{ $final := "" }}
{{ if hugo.IsProduction }}
{{/* Only rebuild concatenated files in production (in dev there is no concat) */}}
{{/* Only minify in production to allow source maps */}}
{{ $final = $script | js.Build "main.js" | minify }}
{{ else }}
{{ $final = $script }}
{{ end }}
<script src="{{ $final.Permalink }}"></script>
<!-- Add Google Analytics v4 tag if not using GTM -->
{{ if and (not .Site.IsServer) (not .Site.Params.gtm_id) }}
{{ template "_internal/google_analytics.html" . }}
{{ end }}

View File

@ -0,0 +1,7 @@
<p class="fallback-text">
{{- with .Site.Params.fallback_text -}}
{{- . -}}
{{- else -}}
No content available yet. Coming soon.
{{- end -}}
</p>

View File

@ -0,0 +1,19 @@
{{ $images := $.Resources.ByType "image" -}}
{{ $custom_file := .Params.feature_image }}
{{ $custom_image := $images.GetMatch $custom_file }}
{{ $feature_image := $images.GetMatch "*feature*" -}}
{{ $img_src := "" }}
{{ with $custom_image }}
{{ $img_src = .RelPermalink }}
{{ else }}
{{ with $feature_image }}
{{ $img_src = .RelPermalink }}
{{ end }}
{{ end }}
{{ if $img_src }}
<img class="feature-image"
srcset="{{ $img_src }} 480w, {{ $img_src }} 800w"
sizes="(max-width: 600px) 480px, 800px"
src="{{ $img_src }}"
{{ with $.Params.feature_image_alt }}alt="{{ . }}"{{ else }}alt="Feature image"{{ end }}>
{{ end }}

View File

@ -0,0 +1,3 @@
{{ if and (eq .Params.draft true) (eq .Site.Params.label_drafts true) (.Site.IsServer) }}
<span class="draft"></span>
{{ end }}

View File

@ -0,0 +1,58 @@
<!-- Social Share Button HTML -->
<div class="social-links">
<ul class="social-icons">
<!-- Twitter -->
{{ with .Site.Params.social.links.twitter }}
<li>
<a href="https://twitter.com/{{ . }}" target="_blank" rel="noopener" aria-label="Visit Twitter profile" class="social-btn twitter">
{{ partial "svg/twitter.svg" }}
</a>
</li>
{{ end }}
<!-- Mastodon -->
{{ if .Site.Params.social.links.mastodon_user }}
<li>
<a href="https://{{ with .Site.Params.social.links.mastodon_server }}{{ . }}{{ else }}mastodon.social{{ end }}/@{{ with .Site.Params.social.links.mastodon_user }}{{ . }}{{ end }}" target="_blank" rel="me noopener" aria-label="Visit Mastodon profile" class="social-btn mastodon">
{{ partial "svg/mastodon.svg" }}
</a>
</li>
{{ end }}
<!-- Github -->
{{ with .Site.Params.social.links.github }}
<li>
<a href="https://github.com/{{ . }}" target="_blank" rel="noopener" aria-label="Visit Github profile" class="social-btn github">
{{ partial "svg/github.svg" }}
</a>
</li>
{{ end }}
<!-- Stack Overflow -->
{{ with .Site.Params.social.links.stack_overflow }}
<li>
<a href="https://stackoverflow.com/users/{{ . }}" target="_blank" rel="noopener" aria-label="Visit Stack Overflow profile" class="social-btn stack-overflow">
{{ partial "svg/stack-overflow.svg" }}
</a>
</li>
{{ end }}
<!-- LinkedIn -->
{{ with .Site.Params.social.links.linkedin }}
<li>
<a href="https://www.linkedin.com/in/{{ . }}" target="_blank" rel="noopener" aria-label="Visit LinkedIn profile" class="social-btn linkedin">
{{ partial "svg/linkedin.svg" }}
</a>
</li>
{{ end }}
<!-- Email -->
{{ with .Site.Params.social.links.email }}
<li>
<a href="mailto:?to={{ . }}" target="_blank" class="social-btn email">
{{ partial "svg/email.svg" }}
</a>
</li>
{{ end }}
</ul>
</div>

View File

@ -0,0 +1,7 @@
<!-- Favicon -->
{{ if eq .Site.Params.favicon true }}
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
{{ end }}

View File

@ -0,0 +1,11 @@
{{ if not .Site.IsServer }}
{{ with .Site.Params.gtm_id }}
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','{{ . }}');</script>
<!-- End Google Tag Manager -->
{{ end }}
{{ end }}

View File

@ -0,0 +1,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
{{ partial "head/resource-hints.html" . }}
{{ partial "head/styles.html" . }}
{{ partial "head/seo/seo.html" . }}
{{ partial "head/favicons.html" . }}
{{ partial "head/scripts.html" . }}
</head>

View File

@ -0,0 +1 @@
<!-- Preload fonts, etc. -->

View File

@ -0,0 +1,4 @@
<!-- Google Tag Manager -->
{{ if .Site.Params.gtm_id }}
{{ partial "head/gtm.html" . }}
{{ end }}

View File

@ -0,0 +1,72 @@
<meta property="og:locale" content="{{ .Site.Params.ogLocale }}">
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
<meta property="og:title" content="{{- with .Params.seo_title -}}{{- . -}}{{ else }}{{- .Title -}}{{ end }}{{ if (or (and (ne .Type "posts") (ne .Type "projects") (not .IsHome)) (and (not .IsPage) (not .IsHome))) }} | {{ .Site.Title -}}{{ end -}}">
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}">
{{ if $.Scratch.Get "paginator" -}}
{{ $paginator := .Paginate (where .Site.RegularPages.ByDate.Reverse "Section" "posts" ) -}}
<meta property="og:url" content="{{ .Paginator.URL | absURL }}">
{{ else -}}
<meta property="og:url" content="{{ .Permalink }}">
{{ end -}}
{{ with .Site.Params.title -}}
<meta property="og:site_name" content="{{ . }}">
{{ end -}}
{{ $iso8601 := "2006-01-02T15:04:05-07:00" -}}
{{ if .IsPage -}}
{{ if not .PublishDate.IsZero -}}
<meta property="article:published_time" {{ .PublishDate.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ else if not .Date.IsZero -}}
<meta property="article:published_time" {{ .Date.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ end -}}
{{ if not .Lastmod.IsZero -}}
<meta property="article:modified_time" {{ .Lastmod.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ end -}}
{{ else -}}
{{ if not .Date.IsZero -}}
<meta property="og:updated_time" {{ .Lastmod.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ end -}}
{{ end -}}
{{ $images := $.Resources.ByType "image" -}}
{{ $feature := $images.GetMatch "*feature*" -}}
{{ $feature_param := $.Params.feature_image }}
{{ $feature_frontmatter := $images.GetMatch $feature_param }}
{{ if $feature_frontmatter -}}
<meta property="og:image" content="{{ $feature_frontmatter.Permalink }}"/>
{{ with $.Params.feature_image_alt }}
<meta property="og:image:alt" content="{{ . }}" />
{{ end }}
{{ else if $feature -}}
<meta property="og:image" content="{{ $feature.Permalink }}"/>
{{ with $.Params.feature_image_alt }}
<meta property="og:image:alt" content="{{ . }}" />
{{ end }}
{{ else if $.Params.images }}
<meta name="og:image" content="{{ index $.Params.images 0 | absURL }}"/>
{{ else if $.Site.Params.images }}
<meta name="og:image" content="{{ index $.Site.Params.images 0 | absURL }}"/>
{{ end }}
{{ with .Params.audio -}}
<meta property="og:audio" content="{{ . | absURL }}">
{{ end -}}
{{ with .Params.videos -}}
{{ range . -}}
<meta property="og:video" content="{{ . | absURL }}">
{{ end -}}
{{ end -}}
{{- /* If it is part of a series, link to related articles */}}
{{- $permalink := .Permalink }}
{{- $siteSeries := .Site.Taxonomies.series }}
{{/* Only add property if taxonomy is enabled */}}
{{ if $siteSeries }}
{{ with .Params.series }}{{- range $name := . }}
{{- $series := index $siteSeries ($name | urlize) }}
{{- range $page := first 6 $series.Pages }}
{{- if ne $page.Permalink $permalink }}<meta property="og:see_also" content="{{ $page.Permalink }}" />{{ end }}
{{- end }}
{{ end }}{{ end }}
{{ end }}

View File

@ -0,0 +1,65 @@
<!-- Robots meta -->
{{ if eq .Kind "404" -}}
<meta name="robots" content="noindex, follow">
{{ else -}}
{{ with .Params.robots -}}
<meta name="robots" content="{{ . }}">
{{ else -}}
<meta name="robots" content="index, follow">
<meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
<meta name="bingbot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
{{ end -}}
{{ end -}}
<!-- SEO title from config or front matter -->
<!-- Site title gets added EXCEPT on single post and project pages -->
<title>
{{- $paginator := .Paginate .RegularPagesRecursive }}
{{- with .Params.seo_title -}}
{{- . -}}
{{- else -}}
{{- .Title -}}
{{- end -}}
{{- with $paginator -}}
{{ if and (gt $paginator.TotalPages 1) (gt $paginator.PageNumber 1) }}
| Page {{ $paginator.PageNumber }}
{{- end -}}
{{- end -}}
{{ if (or (and (ne .Type "posts") (ne .Type "projects") (not .IsHome)) (and (not .IsPage) (not .IsHome))) }} | {{ .Site.Title -}}{{ end -}}
</title>
<!-- Author from config -->
{{ with .Params.author -}}
<meta name="author" content="{{ . }}">
{{ else -}}
<meta name="author" content="{{ .Site.Params.author }}">
{{ end -}}
<!-- Description from config or front matter -->
{{ with .Description -}}
<meta name="description" content="{{ . }}">
{{ else -}}
<meta name="description" content="{{ .Site.Params.description }}">
{{ end -}}
{{ if $.Scratch.Get "paginator" }}
<link rel="canonical" href="{{ .Paginator.URL | absURL }}">
{{ if .Paginator.HasPrev -}}
<link rel="prev" href="{{ .Paginator.Prev.URL | absURL }}">
{{ end -}}
{{ if .Paginator.HasNext -}}
<link rel="next" href="{{ .Paginator.Next.URL | absURL }}">
{{ end -}}
{{ else -}}
<link rel="canonical" href="{{ .Permalink }}">
{{ end -}}
{{ partial "head/seo/opengraph.html" . }}
{{ partial "head/seo/twitter-cards.html" . }}
<!-- Add rss+xml functionality -->
{{- with .OutputFormats.Get "rss" -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s">` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{- end -}}
{{ partial "head/seo/structured-data.html" . }}

View File

@ -0,0 +1,159 @@
{{ $baseURL := "/" | absURL -}}
{{ $dot := . -}}
{{ $dot.Scratch.Set "path" "" -}}
{{ $dot.Scratch.Set "breadcrumb" slice -}}
{{ $url := replace .Permalink ( printf "%s" .Site.BaseURL) "" -}}
{{ $.Scratch.Add "path" .Site.BaseURL -}}
{{ $.Scratch.Add "breadcrumb" (slice (dict "url" .Site.BaseURL "name" "home" "position" 1 )) -}}
{{ range $index, $element := split $url "/" -}}
{{ $dot.Scratch.Add "path" $element -}}
{{ $.Scratch.Add "path" "/" -}}
{{ if ne $element "" -}}
{{ $.Scratch.Add "breadcrumb" (slice (dict "url" ($.Scratch.Get "path") "name" . "position" (add $index 2))) -}}
{{ end -}}
{{ end -}}
{{ $images := $.Resources.ByType "image" -}}
{{ $feature := $images.GetMatch "*feature*" -}}
{{ $feature_param := $.Params.feature_image }}
{{ $feature_frontmatter := $images.GetMatch $feature_param }}
{{ if $feature_frontmatter -}}
{{ $.Scratch.Set "primaryImage" $feature_frontmatter.Permalink }}
{{ with $.Params.feature_image_alt }}
{{ $.Scratch.Set "primaryImageAlt" . }}
{{ end }}
{{ else if $feature -}}
{{ $.Scratch.Set "primaryImage" $feature.Permalink }}
{{ with $.Params.feature_image_alt }}
{{ $.Scratch.Set "primaryImageAlt" . }}
{{ end }}
{{ else if $.Params.images }}
{{ $.Scratch.Set "primaryImage" ( index $.Params.images 0 | absURL ) }}
{{ else if $.Site.Params.images }}
{{ $.Scratch.Set "primaryImage" ( index $.Site.Params.images 0 | absURL ) }}
{{ end }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Person",
"@id": {{ print $baseURL "#/schema/person/1" }},
"name": {{ .Site.Params.schemaName }},
"url": {{ print $baseURL }},
"image": {
"@type": "ImageObject",
"@id": {{ print $baseURL "#/schema/image/1"}},
"url": {{ print $baseURL .Site.Params.schemaImage }},
"width": {{- .Site.Params.schemaImageWidth -}},
"height": {{- .Site.Params.schemaImageHeight -}},
"caption": {{ .Site.Params.schemaName }}
}
},
{
"@type": "WebSite",
"@id": {{ print $baseURL "#/schema/website/1" }},
"url": {{ print $baseURL }},
"name": {{ .Site.Title }},
"description": {{ .Site.Params.description }},
"publisher": {
"@id": {{ print $baseURL "#/schema/person/1" }}
}
},
{
{{ if and (ne .Kind "taxonomy") (ne .Kind "term") -}}
"@type": "WebPage",
{{ else -}}
"@type": "CollectionPage",
{{ end -}}
"@id": {{ .Permalink }},
"url": {{ .Permalink }},
"name": {{ with .Title }}{{ . }}{{ else }}{{ .Site.Title }}{{ end }},
"description": {{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }},
"isPartOf": {
"@id": {{ print $baseURL "#/schema/website/1" }}
},
"about": {
"@id": {{ print $baseURL "#/schema/person/1" }}
},
"datePublished": {{ with .PublishDate}}{{ .Format "2006-01-02T15:04:05-07:00" }}{{ else }}{{ .Date.Format "2006-01-02T15:04:05-07:00" }}{{ end }},
"dateModified": {{ .Lastmod.Format "2006-01-02T15:04:05-07:00" }},
"breadcrumb": {
"@id": {{ print .Permalink "#/schema/breadcrumb/1" }}
},
"primaryImageOfPage": {
"@id": {{ print .Permalink "#/schema/image/2" }}
},
"inLanguage": {{ .Site.Params.schemaLocale }},
"potentialAction": [{
"@type": "ReadAction", "target": [{{ .Permalink }}]
}]
},
{
"@type": "BreadcrumbList",
"@id": {{ print .Permalink "#/schema/breadcrumb/1" }},
"name": "Breadcrumbs",
"itemListElement": [{{ $list := $.Scratch.Get "breadcrumb" }}{{ $len := (len $list) }}{{ range $index, $element := $list }}{{ if ne .position 1 }},{{ end }}{
"@type": "ListItem",
"position": {{ .position }},
"item": {
{{ if ne (add $index 1) $len -}}
"@type": "WebPage",
"@id": {{ .url }},
"url": {{ .url }},
"name": {{ .name | humanize | title }}
{{ else -}}
"@id": {{ .url }}
{{ end -}}
}
}{{ end }}]
},
{{ if and (eq .Kind "page") (eq .Section "posts") -}}
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Article",
"@id": {{ print $baseURL "#/schema/article/1" }},
"headline": {{ .Title }},
"description": {{ .Description }},
"isPartOf": {
"@id": {{ .Permalink }}
},
"mainEntityOfPage": {
"@id": {{ .Permalink }}
},
"datePublished": {{ .PublishDate.Format "2006-01-02T15:04:05-07:00" }},
"dateModified": {{ .Lastmod.Format "2006-01-02T15:04:05-07:00" }},
"author": {
"@id": {{ print $baseURL "#/schema/person/1" }}
},
"publisher": {
"@id": {{ print $baseURL "#/schema/person/1" }}
},
"image": {
"@id": {{ print .Permalink "#/schema/image/2" }}
}
}
]
},
{{- end -}}
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "ImageObject",
"@id": {{ print .Permalink "#/schema/image/2" }},
"url": {{ $.Scratch.Get "primaryImage" }},
"contentUrl": {{ $.Scratch.Get "primaryImage" }},
"caption": {{ with $.Scratch.Get "primaryImageAlt" }}{{ . }}{{ else }}{{ .Title }}{{ end }}
}
]
}
]
}
</script>

View File

@ -0,0 +1,33 @@
{{ with .Site.Params.twitterSite }}
<meta name="twitter:site" content="{{ . }}">
{{ end }}
{{ with .Site.Params.twitterCreator }}
<meta name="twitter:creator" content="{{ . }}">
{{ end }}
<meta name="twitter:title" content="{{- with .Params.seo_title -}}{{- . -}}{{ else }}{{- .Title -}}{{ end }}{{ if (or (and (ne .Type "posts") (ne .Type "projects") (not .IsHome)) (and (not .IsPage) (not .IsHome))) }} | {{ .Site.Title -}}{{ end -}}">
<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}">
{{ $images := $.Resources.ByType "image" -}}
{{ $feature := $images.GetMatch "*feature*" -}}
{{ $feature_param := $.Params.feature_image }}
{{ $feature_frontmatter := $images.GetMatch $feature_param }}
{{ if $feature_frontmatter -}}
<meta name="twitter:card" content="summary_large_image">
<meta property="twitter:image" content="{{ $feature_frontmatter.Permalink }}"/>
{{ with $.Params.feature_image_alt }}
<meta name="twitter:image:alt" content="{{ . }}">
{{ end }}
{{ else if $feature -}}
<meta name="twitter:card" content="summary_large_image">
<meta property="twitter:image" content="{{ $feature.Permalink }}"/>
{{ with $.Params.feature_image_alt }}
<meta name="twitter:image:alt" content="{{ . }}">
{{ end }}
{{ else if $.Params.images }}
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="{{ index $.Params.images 0 | absURL }}"/>
{{ else if $.Site.Params.images }}
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="{{ index $.Site.Params.images 0 | absURL }}"/>
{{ else }}
<meta name="twitter:card" content="summary">
{{ end }}

View File

@ -0,0 +1,34 @@
{{ $sass := resources.Get "scss/main.scss" }}
{{ $options := (dict "enableSourceMap" (not hugo.IsProduction)) }}
{{ $style := $sass | resources.ToCSS $options }}
{{ $custom := "" }}
{{ $custom_no_bundle := false }}
{{ with .Site.Params.custom_css }}
{{ $custom = resources.Get . }}
{{/* Only concatenate in production to allow source maps */}}
{{ if hugo.IsProduction }}
{{ $bundle := slice $style $custom | resources.Concat "main.css" }}
{{ $style = $bundle }}
{{ else }}
{{ $custom_no_bundle = true }}
{{ end }}
{{ end }}
{{ $final := "" }}
{{/* Only enable PostCSS in production to allow source maps */}}
{{ if and (eq $.Site.Params.enable_postcss true) (hugo.IsProduction) }}
{{ $final = $style | postCSS (dict "config" "postcss.config.js") }}
{{ else }}
{{ $final = $style }}
{{ end }}
{{ if hugo.IsProduction }}
{{/* Only minify in production to allow source maps */}}
{{ $final = $final | minify }}
{{ end }}
<link rel="stylesheet" href="{{ $final.Permalink }}" />
{{/* Custom goes last to preserve cascade in development */}}
{{ if $custom_no_bundle }}
<link rel="stylesheet" href="{{ $custom.Permalink }}" />
{{ end }}

View File

@ -0,0 +1,36 @@
<header class="container">
<nav class="main-nav" id="js-navbar">
<a class="logo" href="{{ .Site.BaseURL }}">{{ .Site.Title }}</a>
<ul class="menu" id="js-menu">
{{ $currentPage := . }}
{{ range .Site.Menus.main }}
{{ if .HasChildren }}
<li class="menu-item">
<span class="menu-link">{{ .Name }}<span class="drop-icon"></span></span>
<ul class="sub-menu">
{{ range .Children }}
<li class="menu-item">
<a href="{{ .URL }}" class="menu-link">{{ .Name }}</a>
</li>
{{ end }}
</ul>
</li>
{{ else }}
<li class="menu-item">
<a href="{{ .URL }}" class="menu-link">{{ .Name }}</a>
</li>
{{ end }}
{{ end }}
<li class="menu-item--align">
<div class="switch">
<input class="switch-input" type="checkbox" id="themeSwitch">
<label aria-hidden="true" class="switch-label" for="themeSwitch">On</label>
<div aria-hidden="true" class="switch-marker"></div>
</div>
</li>
</ul>
<span class="nav-toggle" id="js-navbar-toggle">
<svg xmlns="http://www.w3.org/2000/svg" id="Outline" viewBox="0 0 24 24" width="30" height="30" fill="var(--color-contrast-high)"><rect y="11" width="24" height="2" rx="1"/><rect y="4" width="24" height="2" rx="1"/><rect y="18" width="24" height="2" rx="1"/></svg>
</span>
</nav>
</header>

View File

@ -0,0 +1,13 @@
{{ if or (.PrevInSection) (.NextInSection) }}
<section>
<h2>Read Next</h2>
<div class="single-next-previous">
{{ with .PrevInSection }}
<a class="previous" href="{{.Permalink}}">&laquo; {{ .Title }}</a>
{{ end }}
{{ with .NextInSection }}
<a class="next" href="{{.Permalink}}">{{ .Title }} &raquo;</a>
{{ end }}
</div>
</section>
{{ end }}

View File

@ -0,0 +1,23 @@
<div class="post-entry">
<h3 class="post-list-title">
<a href="{{ .Permalink }}">{{ .Title }}</a>
</h3>
<div class="post-list-meta">
{{ partial "posts/post-meta.html" . }}
{{ with .Params.categories }}
<div class="post-list-categories">
{{ range . }}
<a href="{{ "categories" | absURL }}/{{ . | urlize }}/">{{ . }}</a>
{{ end }}
</div>
{{ end }}
{{ partial "general/label-drafts.html" . }}
</div>
<p class="post-list-summary">
{{- with .Params.Summary -}}
{{- . -}}
{{- else -}}
{{- .Description -}}
{{- end -}}
</p>
</div>

View File

@ -0,0 +1,15 @@
<div class="post-list-dates">
{{- if not (eq .Lastmod .Date) -}}
Posted:
{{ end -}}
{{- if not .PublishDate.IsZero -}}
{{- .PublishDate.Format "Jan 2, 2006" -}}
{{- else -}}
{{ .Date.Format "Jan 2, 2006" -}}
{{- end -}}
{{- if not (eq .Lastmod .Date) -}}
&nbsp;&middot;&nbsp;
Updated: {{ .Lastmod.Format "Jan 2, 2006" -}}
{{- end -}}
&nbsp;&middot;&nbsp;
{{- .ReadingTime }} min.</div>

View File

@ -0,0 +1,14 @@
<!-- Reusable template for related posts (at the end of posts single pages) -->
{{ if .Param "related" }}
{{ $related := .Site.RegularPages.Related . | first 5 }}
{{ with $related }}
<section class="related">
<h2>See Also</h2>
<ul class="related-posts">
{{ range . }}
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end }}
</ul>
</section>
{{ end }}
{{ end }}

View File

@ -0,0 +1,44 @@
<!-- Social Share Button HTML -->
{{- if .Param "social_share" }}
{{ $title := .Title }}
{{ $url := printf "%s" .Permalink }}
{{ $body := print $title ", by " .Site.Title "\n" .Params.description "\n\n" $url "\n" }}
<section>
<h2>Share</h2>
<div class="social-links">
<ul class="social-icons--share">
<!-- Twitter -->
{{ if .Site.Params.social.share.twitter }}
<a href="https://twitter.com/intent/tweet?url={{ .Permalink }}&amp;text={{ .Title }}" target="_blank" rel="noopener" aria-label="Share on Twitter" class="social-btn twitter">
<li>{{ partial "svg/twitter.svg" }}</li>
</a>
{{ end }}
<!-- Reddit -->
{{ if .Site.Params.social.share.reddit }}
<a href="https://www.reddit.com/submit?url={{ .Permalink }}" target="_blank" rel="noopener" aria-label="Share on Reddit" class="social-btn reddit">
<li>{{ partial "svg/reddit.svg" }}</li>
</a>
{{ end }}
<!-- Facebook -->
{{ if .Site.Params.social.share.facebook }}
<a href="https://www.facebook.com/sharer.php?u={{ $url }}" target="_blank" rel="noopener" aria-label="Share on Facebook" class="social-btn facebook">
<li>{{ partial "svg/facebook.svg" }}</li>
</a>
{{ end }}
<!-- LinkedIn -->
{{ if .Site.Params.social.share.linkedin }}
<a href="https://www.linkedin.com/shareArticle?mini=true&amp;url={{ $url }}&amp;source={{ $url }}&amp;title={{ $title }}&amp;summary={{ $title }}" target="_blank" rel="noopener" aria-label="Share on LinkedIn" class="social-btn linkedin">
<li>{{ partial "svg/linkedin.svg" }}</li>
</a>
{{ end }}
<!-- Email -->
{{ if .Site.Params.social.share.email }}
<a href="mailto:?subject={{ .Site.Title }}%20-%20{{ $title }}.&amp;body={{ $body }}" target="_blank" class="social-btn email">
<li>{{ partial "svg/email.svg" }}</li>
</a>
</ul>
</div>
</section>
{{ end }}
{{- end }}

Some files were not shown because too many files have changed in this diff Show More