Big update: add quiz function, new post

This commit is contained in:
Nathan Upchurch 2025-02-02 17:40:57 -06:00
parent a98006f918
commit a915341e5c
35 changed files with 2249 additions and 115 deletions

View File

@ -311,10 +311,17 @@ export default {
},
{
title: "Reveal from the Center for Investigative Reporting",
feedUrl: "https://revealnews.org/feed",
url: "https://revealnews.org/",
feedUrl: "https://contrarian.substack.com/feed",
url: "https://contrarian.substack.com/",
description:
'Reveal is an investigative radio show and podcast that holds the powerful accountable by reporting about everything from racial and social injustices to threats to public safety and democracy. (Thanks to <a href="https://werd.io/2024/non-profit-newsrooms-that-speak-to-power">werd.io</a> for the recommendation.)',
"Democracy faces an unprecedented threat from an authoritarian movement built on lies and contempt for the rule of law. The first and most critical defense of democracy—a robust, independent free press—has been missing in action. Corporate and billionaire media owners have shied away from confrontation, engaged in false equivalence, and sought to curry favor with Donald Trump. It is hardly surprising that readers and viewers are fleeing from these outlets. Americans need an alternative. The Contrarian is that alternative: unflinching, unapologetic, and unwavering in its commitment to truth-telling.",
},
{
title: "The Contrarian",
feedUrl: "https://themarkup.org/feeds/rss.xml",
url: "https://contrarian.substack.com/",
description:
'The Markup investigates how powerful institutions are using technology to change our society. (Thanks to <a href="https://werd.io/2024/non-profit-newsrooms-that-speak-to-power">werd.io</a> for the recommendation.)',
},
{
title: "The Markup",

View File

@ -14,7 +14,7 @@ export default {
profilePic: "/img/CN20191025_301_Srt_SQUARE_crop.jpg",
},
blogrollUrl: "/blogroll/nathanUpchurchBlogroll.opml",
copyrightNotice: "© Nathan Upchurch 2022 - 2024",
copyrightNotice: "© Nathan Upchurch 2022 - 2025",
defaultPostImageURL: "/img/logo_post.svg",
defaultPostImageAlt: "The logo for this blog: a capital letter N.",
mastodonHost: "lounge.town",

View File

@ -2,10 +2,9 @@
<html lang="{{ metadata.language }}">
<head>
{% include "metadata.njk" %}
{#- Bundle CSS #}
{%- css %}{% include "public/css/index.css" %}{% endcss %}
{%- css %}{% include "public/css/webfonts/webfonts.css" %}{% endcss %}
<style>{% getBundle "css" %}</style>
<link rel="stylesheet" type="text/css" href="/css/index.css" />
<link rel="stylesheet" type="text/css" href="/css/webfonts/webfonts.css" />
<link rel="stylesheet" type="text/css" href="/css/code.css" />
{% include "structuredData.njk" %}
{% include "umami.html" %}
</head>

View File

@ -1,21 +0,0 @@
<!doctype html>
<html lang="{{ metadata.language }}">
<head>
{% include "metadata.njk" %}
{#- Bundle CSS #}
{%- css %}{% include "public/css/index.css" %}{% endcss %}
{%- css %}{% include "public/css/webfonts/webfonts.css" %}{% endcss %}
<style>{% getBundle "css" %}</style>
{% include "structuredData.njk" %}
{% include "umami.html" %}
</head>
<body>
{% include "header.njk" %}
<main id="skip">
<section class="full-width-text">
{{ content | safe }}
</section>
</main>
{% include "footer.njk" %}
</body>
</html>

View File

@ -1,7 +1,6 @@
---
layout: layouts/baseBareBones.njk
layout: layouts/linksPage.njk
---
{%- css %}{% include "public/css/me.css" %}{% endcss %}
<div class="links-container">
<img class="profilePic" src="{{ metadata.author.profilePic }}">
<h1 class="socialTitle">Nathan Upchurch</h1>

View File

@ -2,10 +2,10 @@
<html lang="{{ metadata.language }}" class="barebones">
<head>
{% include "metadata.njk" %}
{#- Bundle CSS #}
{%- css %}{% include "public/css/index.css" %}{% endcss %}
{%- css %}{% include "public/css/webfonts/webfonts.css" %}{% endcss %}
<style>{% getBundle "css" %}</style>
<link rel="stylesheet" type="text/css" href="/css/me.css" />
<link rel="stylesheet" type="text/css" href="/css/index.css" />
<link rel="stylesheet" type="text/css" href="/css/webfonts/webfonts.css" />
{% include "umami.html" %}
</head>
<body class="barebones">

View File

@ -1,8 +1,6 @@
---
layout: layouts/base.njk
---
{# Only include the syntax highlighter CSS on blog posts #}
{%- css %}{% include "public/css/code.css" %}{% endcss %}
<article class="post">
<h1>{{ title | safe }}</h1>
{% if not hideMetadata %}

View File

@ -0,0 +1,53 @@
---
layout: layouts/post.njk
structuredData: none
---
{{ content | safe }}
<section class="quiz">
<form onsubmit="handleQuizSubmit(); return false">
{% for question in questions %}
{% set q = loop.index %}
<div class="questionBox">
<p class="quizQuestion">{{ q }}. {{ question.title }}</p>
{% if question.image %}
<figure>
<a href="{{ question.image }}">
<img src="{{ question.image }}" alt="{{ question.imageAlt }}">
</a>
{% if question.imageCaption %}
<figcaption>{{ question.imageCaption }}</figcaption>
{% endif %}
</figure>
{% endif %}
<div class="answersBox">
{% for answer in question.answers %}
<div class="answerBox">
<input class="answer" type="radio" value="{{ answer.points }}" id="q{{ q }}a{{ loop.index }}" name="{{ q }}" required>
<label for="q{{ q }}a{{ loop.index }}">{{ answer.name }}</label>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
<script src="/js/quiz.js"></script>
<button>Submit</button>
</form>
</section>
{% for consequence in consequences %}
<dialog class="consequence" data-points-threshold="{{ consequence.points }}">
<h2>{{ consequence.title }}</h2>
<p>{{ consequence.spiel }}</p>
{% if consequence.image %}
<img src="{{ consequence.image }}" alt="{{ consequence.imageAlt }}">
{% endif %}
<details>
<summary>Score Details</summary>
<p class="scoreDetails"></p>
</details>
<form method="dialog">
<button>Thanks</button>
</form>
</dialog>
{% endfor %}

View File

@ -4,6 +4,7 @@
<link rel="icon" type="image/x-icon" href="/img/logo_favicon.svg">
<link rel="blogroll" type="text/xml" href="{{ metadata.blogrollUrl }}">
<meta name="description" content="{{ description or metadata.description }}">
<meta name="image" content="{{ metadata.url }}{{ imageURL or metadata.author.profilePic }}">
<meta name="robots" content="noai, noimageai">
<meta name="generator" content="{{ eleventy.generator }}">
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="{{ metadata.title }}">

View File

@ -22,15 +22,4 @@
"url": "{{ page.url | htmlBaseUrl(metadata.url) }}",
}
</script>
<!-- Open Graph -->
<meta property="og:type" content="article" />
<meta property="og:title" content="{{ title }}" />
<meta property="og:description" content="{{ synopsis}}" />
<meta property="og:url" content="{{ page.url | htmlBaseUrl(metadata.url) }}" />
<meta property="og:image" content="{% if imageURL %}{{ imageURL | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}" />
<!-- Twitter card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{ title }}" />
<meta name="twitter:description" content="{{ synopsis}}" />
<meta name="twitter:image" content="{% if imageURL %}{{ imageURL | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}" />
{% endif %}

View File

@ -1,5 +1,5 @@
---
layout: layouts/base_full_width_text.njk
layout: layouts/base.njk
title: Nathan Upchurch | Colophon
structuredData: none
---

View File

@ -1,5 +1,5 @@
---
layout: layouts/base_full_width_text.njk
layout: layouts/base.njk
title: Nathan Upchurch | Privacy
structuredData: none
---

View File

@ -1,5 +1,5 @@
---
layout: layouts/base_full_width_text.njk
layout: layouts/base.njk
title: Nathan Upchurch | AI
structuredData: none
---

View File

@ -0,0 +1,28 @@
---
title: "Rammstein Incense Cones: A Review"
description: "It's only natural that the pyrotechnics obsessed neue deutsche härte act would release a line of incense cones. Today I'm taking a look."
date: 2025-02-02
tags:
- Incense
- Incense Review
synopsis: "It's only natural that the pyrotechnics obsessed neue deutsche härte act would release a line of incense cones. Today I'm taking a look."
imageURL: /img/rammsteinShow_copy.webp
imageAlt: A shot from a Rammstein stadium tour showing the stage and great plumes of smoke from the pyrotechnics.
mastodon_id: "113936913424530239"
draft: true
---
The German neue deutsche härte group Rammstein is known for many things. From their [controversial lyrics (NSFW)](https://www.reuters.com/article/lifestyle/rammstein-album-banned-from-display-in-germany-idUSTRE5A90ZK/) and [legally dubious stage antics (NSFW)](https://www.revolvermag.com/music/see-rammsteins-infamous-1998-family-values-show-landed-members-jail/), to their [over the top live performances](https://metalinjection.net/news/rammsteins-pyro-guy-discusses-the-insanity-of-his-job-how-much-fuel-the-band-uses), one thing the group is certainly *not* known for is conventional merchandise.
Far from the usual assortment of posters and t-shirts, the band's merch has ranged from [medical supplies](https://www.rammsteinshop.us/en/catalog/wound-plaster-rammplast.html) to [kitchen tools](https://www.rammsteinshop.us/en/catalog/cookie-shape-zerdrucken.html), [furniture](https://shop.rammstein.de/en/catalog/kreuztisch-oak.html), [torches](https://shop.rammstein.de/en/catalog/fire-torch-funkenstoss.html), and [*very* special editions of their albums (NSFW)](https://www.rammstein.de/en/news/rammstein-deluxe-2/). Despite this, I was inexplicably surprised to discover that the group had released official, Rammstein branded incense cones for sale on the band's online shop.
At $5.00 for a box of 24, I didn't have high expectations. The picture on [the website](https://www.rammsteinshop.us/en/catalog/incense-candles-rammstein.html) showed a handful of crudely formed black cones, the color likely due to a high charcoal content, which often indicates that the fragrance is constructed from oils rather than whole plant ingredients. Realistically, I wouldn't expect anything else at this price point. The website lists cedar, sandalwood, juniper wood, rosemary, juniper berries, myrrh, frankincense, and benzoin as components of the perfume.
The cones are manufactured by [KNOX](https://www.knox.de/), a large German manufacturer of incense cones and those delightful little wooden [incense "smokers"](https://www.youtube.com/watch?v=v0t-mlg2SoA) that I'm told are popular around the holidays in Germany. Steve of the [Incense in The Wind](https://incenseinthewind.blogspot.com/) blog recently wrote a number of reviews for a variety of KNOX cones; I must admit that after reading them I was steeling myself for the arrival of my Rammstein *Räucherkerzen*.
[![A cone burning in a cast iron burner beside the box of cones.](/img/rammstein_incense_cones.webp "They do look pretty metal.")](/img/rammstein_incense_cones.webp)
When the two small packages of cones arrived, they were identical in appearance to the images on the website. Despite being inside of a mailing box, a paper bag, a cardboard carton, and finally sealed inside of a small plastic bag, I could smell the cones before I even opened the outer box. The fragrance was woody; it was juniper-forward with a sharp, turpenous edge, all tied together with a *big* hit of sweet, creamy benzoin. I didn't detect much of the other resins mentioned, but that may be because I'm more used to the actual resin rather than extracts and imitations. I am not usually fond of highly concentrated scents, but I must admit I enjoyed this, even though even the outer packaging of the cones seems to contaminate everything it touches with fragrance—I'll often catch a whiff of these cones in their packaging while just walking around my apartment.
Upon lighting, I'm briefly met with the scent of burning paper and those off-notes typical of charcoal + oil incense, which is not exactly pleasant, but it does make me nostalgic for some of the cheap incense I used to burn as a teenager. Most of the fragrance that was present on the unlit cone has disappeared, leaving mellow cedar and sweet benzoin notes. As [Steve found in his review of KNOX' vanilla cones](https://incenseinthewind.blogspot.com/2025/01/knox-vanille-raucherkerzen-vanilla.html), these also burn hot and fast, with a large ember characteristic of a high charcoal content. Cones predominantly based on wood tend to have an ember that travels down the cone, but here the ember seems to just increase in size until it envelopes the entire cone at once beneath a thin layer of ash. The cones don't put out much smoke. The mild, sweet fragrance in the burn does linger in the room for some time, but it is so diminished from the powerful scent of the unlit cones that I'm not terribly worried about it soaking into the carpet, which is often a concern with cheaper, oil-based incense.
I didn't at all expect to say this, but I enjoy these cones. They are not an example of a high-quality incense, but I enjoy the fragrance despite it all. I suppose the beauty of cones that use highly concentrated fragrances is that they don't last long; I don't know whether I could tolerate an eleven-inch bamboo-cored stick of this, but I can absolutely enjoy a little cone. In addition, despite the off-notes that charcoal introduces, I wonder if it doesn't overcome one of the key challenges of the format: when making traditional incense cones with actual plants, the temperature increases as the ember grows, so by the time you get to the base, the scent can be quite coarse indeed. With these charcoal cones, it seems as though they start *hot* and stay that way, eliminating the challenge of dealing with a dramatic temperature increase over the course of the burn. Yea, somehow, I like these.

View File

@ -1,9 +1,20 @@
---
layout: layouts/base_full_width_text.njk
layout: layouts/base.njk
title: Nathan Upchurch | Changelog
structuredData: none
---
# Changelog
* 2025-02-01
* Implement [quiz features](/quizzes/) and add [first quiz](/quizzes/how-much-of-a-linux-nerd-are-you/).
* 2025-02-01
* Remove support for Open Graph and Twitter Card metadata because A. bloat, and B. screw Musk and Zuck.
* Add "image" meta tag for all pages, using either the image specified for the page / post, or my smiling face as a default.
* Fix issue with metadata output on gallery image pages.
* Stopped bundling CSS and injecting it into pages as I was sick of 1,000 lines of CSS on *every single page* (My build times are now a third of what they were).
* 2025-01-31
* Update the copyright notice in the footer.
* 2025-1-29
* Add [The Contrarian](https://contrarian.substack.com/) to the [blogroll](/blogroll).
* 2025-1-24
* Add buttons / update [/now/](/now/).
* 2025-1-23 - Simplify, simplify, simplify

View File

@ -6,9 +6,10 @@ pagination:
layout: layouts/base.njk
structuredData: none
eleventyComputed:
imageURL: "{{ picture.baseUrl }}/{{ picture.filename }}"
title: "Image: {{ picture.title }}"
permalink: "/gallery/{{ picture.containingGallery | slugify }}/{{ picture.filename | slugify }}/"
description: "{{ picture.title }} from gallery: {{ picture.containingGallery}}"
permalink: "/gallery/{{ picture.containingGallery | slugify }}/{{ picture.filename | slugify }}/"
description: "{{ picture.title }} from gallery: {{ picture.containingGallery}}"
---
<article>
<h1>{{ picture.title }}</h1>

View File

@ -6,59 +6,87 @@ eleventyNavigation:
numberOfLatestPostsToShow: 3
numberOfNowPostsToShow: 1
numberOfGalleriesToShow: 1
numberOfQuizzesToShow: 1
hideGalleryDescriptions: 1
---
<h2>Latest gallery:</h2>
{% set postsCount = galleries | length %}
{% set latestPostsCount = postsCount | min(numberOfNowPostsToShow) %}
{% set postslist = collections.now | head(-1 * numberOfNowPostsToShow) %}
{% set postslistCounter = postsCount %}
{% set showPostListHeader = false %}
{% include "gallerieslist.njk" %}
<section class="indexFeature">
<h2>Latest gallery:</h2>
{% set postsCount = galleries | length %}
{% set latestPostsCount = postsCount | min(numberOfGalleriesToShow) %}
{% set postslist = galleries | head(-1 * numberOfGalleriesToShow) %}
{% set postslistCounter = postsCount %}
{% set showPostListHeader = false %}
{% include "gallerieslist.njk" %}
{% set morePosts = postsCount - numberOfNowPostsToShow %}
{% if morePosts > 0 %}
<a href="/galleries/">
<button type="button">
See {{ morePosts }} more »
</button>
</a>
{% endif %}
<div class="now">
<h2>Life updates:</h2>
{% set postsCount = collections.now | length %}
{% set latestPostsCount = postsCount | min(numberOfNowPostsToShow) %}
{% set postslist = collections.now | head(-1 * numberOfNowPostsToShow) %}
{% set postslistCounter = postsCount %}
{% set showPostListHeader = false %}
{% include "postslist.njk" %}
{% set morePosts = postsCount - numberOfNowPostsToShow %}
{% if morePosts > 0 %}
<a href="/now/">
{% set morePosts = postsCount - numberOfGalleriesToShow %}
{% if morePosts > 0 %}
<a href="/galleries/">
<button type="button">
See {{ morePosts }} more »
</button>
</a>
{% endif %}
</div>
{% endif %}
</section>
{% set postsCount = collections.posts | length %}
{% set latestPostsCount = postsCount | min(numberOfLatestPostsToShow) %}
{% set postslist = collections.posts | head(-1 * numberOfLatestPostsToShow) %}
{% set postslistCounter = postsCount %}
{% set showPostListHeader = true %}
{% include "postslist.njk" %}
<section class="indexFeature">
<h2>Latest quiz:</h2>
{% set postsCount = collections.quiz | length %}
{% set latestPostsCount = postsCount | min(numberOfQuizzesToShow) %}
{% set postslist = collections.quiz | head(-1 * numberOfQuizzesToShow) %}
{% set postslistCounter = postsCount %}
{% set showPostListHeader = false %}
{% include "postslist.njk" %}
{% set morePosts = postsCount - numberOfLatestPostsToShow %}
{% if morePosts > 0 %}
<a href="/blog/">
<button type="button">
See {{ morePosts }} more »
</button>
</a>&nbsp;
{% endif %}
<a href="/tags/">
<button type="button">Topics »</button>
</a>
{% set morePosts = postsCount - numberOfQuizzesToShow %}
{% if morePosts > 0 %}
<a href="/quizzes/">
<button type="button">
See {{ morePosts }} more »
</button>
</a>
{% endif %}
</section>
<section class="indexFeature">
<div class="now">
<h2>Life updates:</h2>
{% set postsCount = collections.now | length %}
{% set latestPostsCount = postsCount | min(numberOfNowPostsToShow) %}
{% set postslist = collections.now | head(-1 * numberOfNowPostsToShow) %}
{% set postslistCounter = postsCount %}
{% set showPostListHeader = false %}
{% include "postslist.njk" %}
{% set morePosts = postsCount - numberOfNowPostsToShow %}
{% if morePosts > 0 %}
<a href="/now/">
<button type="button">
See {{ morePosts }} more »
</button>
</a>
{% endif %}
</div>
</section>
<section class="indexFeature">
{% set postsCount = collections.posts | length %}
{% set latestPostsCount = postsCount | min(numberOfLatestPostsToShow) %}
{% set postslist = collections.posts | head(-1 * numberOfLatestPostsToShow) %}
{% set postslistCounter = postsCount %}
{% set showPostListHeader = true %}
{% include "postslist.njk" %}
{% set morePosts = postsCount - numberOfLatestPostsToShow %}
{% if morePosts > 0 %}
<div class="buttonContainer">
<a href="/blog/">
<button type="button">
See {{ morePosts }} more »
</button>
</a>
{% endif %}
<a href="/tags/">
<button type="button">Topics »</button>
</a>
</div>
</section>

14
content/quizzes.njk Normal file
View File

@ -0,0 +1,14 @@
---
layout: layouts/base.njk
title: Nathan Upchurch | Quizzes
structuredData: none
description: "Bored? Take a fun quiz to while away the time!"
---
<h1>Quizzes</h1>
<p>Test your knowledge, learn about yourself, and waste your precious time, right here, right now, with a quiz!</p>
{% set postsCount = collections.quiz | length %}
<h2>Available Quizzes:</h2>
{% set postslist = collections.quiz %}
{% set showPostListHeader = false %}
{% include "postslist.njk" %}

View File

@ -0,0 +1,302 @@
---
title: "How Much of a Linux Nerd are You?"
description: "A quiz to find out how much of a Linux (or should I say GNU/Linux) nerd you are."
date: 2025-02-02
imageURL: "/img/Tux.svg"
imageAlt: "Tux the penguin."
consequences:
- title: "Why are you here?"
points: 0
spiel: "Look, I don't know how you found yourself here, but I think your time might be better spent elsewhere!"
image: "/img/lost.gif"
imageAlt: "An animated GIF of Cookie Monster looking around in a confused manner and scratching his head."
- title: "You're a beginner"
points: 30
spiel: "You know a couple things, but you're still just getting started on your Linux journey."
image: "/img/noob.gif"
imageAlt: "An animated GIF calling you a noob."
- title: "Linux user detected"
points: 60
spiel: "You are a bona fide Linux user."
image: "/img/tuxfall.gif"
imageAlt: "An animated GIF of a gelatinous Tux falling from the sky and landing with a jiggle."
- title: "You're a Linux pro"
points: 90
spiel: "You ain't scared of the terminal."
image: "/img/matrix.gif"
imageAlt: "An animated GIF of letters falling on a black screen, matrix style."
- title: "Certified Linux nerd"
points: 120
spiel: "Fire up some tendies in celebration, because you are a certified Linux nerd. Frankly, I'm surprised you turned on JavaScript in your browser to complete this quiz!"
image: "/img/tendies.gif"
imageAlt: "An animated GIF of Post Malone eating tendies."
- title: "Linus… is that you?"
points: 150
spiel: "Either you looked at the source code for this page, or you are a GNU/Linux god."
image: "/img/linus.gif"
imageAlt: "An animated GIF of Linus Torvalds speaking and flipping off the camera."
questions:
- title: "What is a kernel?"
image:
imageAlt:
imageCaption:
answers:
- name: "Some sort of seed or pit."
points: 0
- name: "The part of an operating system that bridges software and hardware."
points: 5
- name: "A piece of software that controls the desktop environment."
points: 0
- name: "…another free component of a fully functioning GNU system…"
points: 10
- title: "Which desktop environment is known for its unique workflow and consistent appearance?"
image:
imageAlt:
imageCaption:
answers:
- name: "GNOME"
points: 5
- name: "KDE"
points: 0
- name: "Cinnamon"
points: 0
- name: "Xfce"
points: 0
- title: "Which distro might someone use, BTW?"
image:
imageAlt:
imageCaption:
answers:
- name: "Debian"
points: 0
- name: "openSUSE"
points: 0
- name: "Fedora"
points: 0
- name: "Arch"
points: 5
- title: "Which utensil does :(){ :|:& };: bring to mind?"
image:
imageAlt:
imageCaption:
answers:
- name: "A Chopstick"
points: 0
- name: "A Fork"
points: 5
- name: "A Spoon"
points: 0
- name: "A Knife"
points: 0
- title: "Which of the following distros accidentally launched a DDOS attack on an upstream repo?"
image:
imageAlt:
imageCaption:
answers:
- name: "Garuda"
points: 0
- name: "Nix"
points: 0
- name: "Vanilla OS"
points: 0
- name: "Manjaro"
points: 5
- title: "Complete the sequence: sudo rm -rf"
image:
imageAlt:
imageCaption:
answers:
- name: "/"
points: 5
- name: "*.jpg"
points: 0
- name: "/dev/sda"
points: 0
- name: "--no-preserve-root /"
points: 10
- name: "*/."
points: 0
- title: "What is your favorite package format?"
image:
imageAlt:
imageCaption:
answers:
- name: "Bubble wrap"
points: 0
- name: "Snap"
points: -5
- name: "Flatpak"
points: 5
- name: "Native Packages"
points: 10
- name: "AppImage"
points: 5
- name: "Piping install scripts from GitHub to bash"
points: 0
- title: "Desktop environment or window manager?"
image:
imageAlt:
imageCaption:
answers:
- name: "Desktop environment"
points: 0
- name: "Window manager"
points: 5
- name: "Both are bloat"
points: 10
- title: "From memory, use the tar command to create a compressed tarball. Did you succeed?"
image:
imageAlt:
imageCaption:
answers:
- name: "Yes"
points: 5
- name: "No"
points: 0
- title: "Which command will make a train roll across your terminal?"
image:
imageAlt:
imageCaption:
answers:
- name: "sudo rm -rf /*"
points: 0
- name: "dd if=/dev/zero bs=10G count=10000 | bzip2 -c > train.bz2"
points: 0
- name: ":(){ :|:& };:"
points: 0
- name: "sl"
points: 5
- title: "Which of the following commands will successfully exit Vim?"
image:
imageAlt:
imageCaption:
answers:
- name: "ctrl+q"
points: 0
- name: "exit"
points: 0
- name: ":wq!"
points: 5
- name: ":qv"
points: 0
- title: "SystemD is…"
image:
imageAlt:
imageCaption:
answers:
- name: "A suite of basic building blocks for a Linux system"
points: 5
- name: "A backup in case your Windows dual-boot eats your Linux install."
points: 0
- name: "A utility to mount external drives."
points: 0
- name: "Bloat"
points: 10
- title: "Choose your favorite musical instrument:"
image:
imageAlt:
imageCaption:
answers:
- name: "🎸"
points: 0
- name: "🎷"
points: 0
- name: "GRUB"
points: 10
- name: "🎻"
points: 0
- title: "…won't be big and professional like…?"
image:
imageAlt:
imageCaption:
answers:
- name: "GNU"
points: 5
- name: "Unix"
points: 0
- name: "Windows Vista"
points: 0
- name: "TempleOS"
points: 0
- title: "Which would NOT be a fun surprise for Richard Stallman?"
image:
imageAlt:
imageCaption:
answers:
- name: "Buying a parrot"
points: 5
- name: "A room temperature of 71℉"
points: 0
- name: "Helping him cross the street"
points: 5
- name: "A nice dinner with less than four people total"
points: 0
- title: "What did Microsoft CEO Steve Ballmer call GNU/Linux?"
image:
imageAlt:
imageCaption:
answers:
- name: "A cancer"
points: 5
- name: "An amateur effort"
points: 0
- name: "Delightful"
points: 0
- name: "Bloat"
points: 0
- title: "What is your favorite animal?"
image:
imageAlt:
imageCaption:
answers:
- name: "🦊"
points: 10
- name: "🐧"
points: 5
- name: "🐢"
points: 0
- name: "🪱"
points: 0
- title: "Which distro is named after a husband and wife?"
image:
imageAlt:
imageCaption:
answers:
- name: "Trisquel"
points: 0
- name: "Manjaro"
points: 0
- name: "Ubuntu"
points: 0
- name: "Debian"
points: 5
- title: "Best package to install on a fresh Linux installation?"
image:
imageAlt:
imageCaption:
answers:
- name: "XZ 5.6.0"
points: 20
- name: "Inkscape"
points: 5
- name: "Timeshift"
points: 10
- name: "Photoshop"
points: -5
- title: "Which game console had an official Linux distro that users could buy and install?"
image:
imageAlt:
imageCaption:
answers:
- name: "PlayStation 2"
points: 5
- name: "Nintendo 64"
points: 0
- name: "Nintendo Wii"
points: 0
- name: "Xbox 360"
points: 0
---
Are you *k*onfident in your KDE knowledge? Have you joined us now, shared the software, and spread the good *gn*ews about your newfound freedom? Do you find it *awk*ward when people use proprietary software? No more *Stall*ing; it's time to *find* out whether you're a true GNU/Linux nerd.
*Note: please not type any of commands on this page into your terminal without first looking them up.*

View File

@ -0,0 +1,4 @@
export default {
tags: ["quiz"],
layout: "layouts/quizzes.njk",
};

View File

@ -1,5 +1,5 @@
---
layout: layouts/base_full_width_text.njk
layout: layouts/base.njk
title: Nathan Upchurch | Sitemap
structuredData: none
---
@ -25,5 +25,6 @@ structuredData: none
<li><a href="/now">Now</a></li>
<li><a href="/tags">Tags</a></li>
<li><a href="/wish">Wishes</a></li>
<li><a href="/quizzes">Quizzes</a></li>
</ul>
</section>

View File

@ -1,5 +1,5 @@
---
layout: layouts/base_full_width_text.njk
layout: layouts/base.njk
title: Nathan Upchurch | Wish
structuredData: none
---

View File

@ -199,7 +199,8 @@ export default async function (eleventyConfig) {
eleventyConfig.addFilter("filterTagList", function filterTagList(tags) {
return (tags || []).filter(
(tag) => ["all", "nav", "post", "posts", "gallery"].indexOf(tag) === -1,
(tag) =>
["all", "nav", "post", "posts", "gallery", "quiz"].indexOf(tag) === -1,
);
});

View File

@ -134,6 +134,7 @@
--link-decoration-thickness: 0.1rem;
/* Borders */
--border-details: 1px solid var(--color-gray-20);
--border-hr: 0.5px solid var(--color-gray-20);
--border-nav: 1px solid var(--text-color);
--border-nav-currentpage: var(--space-xs-s) solid var(--contrast-color);
@ -263,13 +264,15 @@ main {
gap: var(--grid-gutter);
grid-template-columns: repeat(var(--grid-columns), 1fr);
}
.indexFeature:not(:last-child) {
padding-bottom: var(--space-l);
}
nav {
grid-column: 2 / span 12;
}
.now {
display: grid;
grid-column: var(--span-grid);
padding: var(--space-l) 0;
}
ol {
padding-left: 0;
@ -277,16 +280,14 @@ ol {
section {
display: grid;
grid-column: var(--span-grid);
&.full-width-text {
p {
grid-column: var(--span-grid);
}
/* Add fleuron to last <p> in section */
> p:not(blockquote > p):last-child:after {
content: "\2766";
display: inline;
font-size: var(--step-1);
}
p {
grid-column: var(--span-grid);
}
/* Add fleuron to last <p> in section */
> p:not(blockquote > p):last-child:after {
content: "\2766";
display: inline;
font-size: var(--step-1);
}
}
::selection {
@ -967,6 +968,7 @@ article.post {
}
}
}
/* Add fleuron after <p> in article when footnotes are present */
p:has(+ hr.footnotes-sep):after {
content: "\2766";
@ -974,6 +976,89 @@ p:has(+ hr.footnotes-sep):after {
font-size: var(--step-1);
}
/* Quiz */
.answerBox {
margin-bottom: var(--space-3xs);
}
.answersBox > input {
display: block;
}
details {
background-color: var(--card-color);
border: var(--border-details);
border-radius: var(--border-radius);
color: var(--text-color);
margin-top: var(--space-s);
padding: var(--space-xs);
width: 100%;
font-size: var(--step--2);
font-variation-settings: var(--font-variation-ui);
text-transform: uppercase;
letter-spacing: var(--ui-letter-spacing);
font-family: var(--font-family-ui);
::marker {
content: "+ ";
}
&[open] p {
font-size: var(--step--2);
line-height: calc(var(--step--2) * 0.25 + var(--step--2));
}
&[open] summary::marker {
content: "- ";
}
&[open] summary {
border-bottom: var(--border-details);
margin-bottom: var(--space-xs);
padding-bottom: var(--space-xs);
}
summary {
font-size: var(--step--2);
font-variation-settings: var(--font-variation-ui);
text-transform: uppercase;
letter-spacing: var(--ui-letter-spacing);
font-family: var(--font-family-ui);
}
}
dialog {
background-color: var(--card-color);
border: none;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
width: var(--grid-max-width);
h2 {
padding-top: 0;
}
p {
color: var(--text-color);
}
&::backdrop {
background-color: var(--contrast-color);
opacity: 0.5;
}
img {
max-width: 100%;
padding-top: var(--space-s);
}
}
.questionBox {
margin: var(--space-s) 0;
figure {
padding-top: 0;
padding-bottom: 0;
}
}
.quizQuestion {
font-size: var(--step-2);
font-variation-settings:
"opsz" 50,
"wght" 350,
"SOFT" 20,
"WONK" 1;
line-height: calc(var(--step-2) * 0.25 + var(--step-2));
padding-bottom: var(--space-s);
padding-top: var(--space-s);
}
/* Utilities */
.grid-container {
max-width: var(--grid-max-width);

1591
public/img/Tux.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 71 KiB

BIN
public/img/linus.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
public/img/lost.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
public/img/matrix.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
public/img/noob.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

BIN
public/img/tendies.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/img/tuxfall.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

43
public/js/quiz.js Normal file
View File

@ -0,0 +1,43 @@
const score = (answers) => {
let total = 0;
let scores = [];
for (let i = 0; i < answers.length; i++) {
const questionNumber = answers[i].name;
if (answers[i].checked) {
total += Number(answers[i].value);
scores.push({
questionNumber: questionNumber,
points: answers[i].value,
});
}
}
return { totalPoints: total, scores: scores };
};
const dishOutConsequences = (consequences, points) => {
for (let i = consequences.length - 1; i >= 0; i--) {
if (points >= Number(consequences[i].dataset.pointsThreshold)) {
consequences[i].showModal();
return;
}
}
};
const populateDetails = (detailsElement, scores, total) => {
detailsElement.innerHTML = `Total Score: ${total} points<br />`;
for (let i = 0; i < scores.length; i++) {
detailsElement.innerHTML += `<br />Question ${scores[i].questionNumber >= 10 ? scores[i].questionNumber : "0" + scores[i].questionNumber}: ${scores[i].points} points`;
}
};
const handleQuizSubmit = () => {
const answers = document.getElementsByClassName("answer");
const consequences = document.getElementsByClassName("consequence");
const details = document.getElementsByClassName("scoreDetails");
const totalPoints = score(answers).totalPoints;
const scoreDetails = score(answers).scores;
for (let i = 0; i < details.length; i++) {
populateDetails(details[i], scoreDetails, totalPoints);
}
dishOutConsequences(consequences, totalPoints);
};