Compare commits

...
Sign in to create a new pull request.

82 commits

Author SHA1 Message Date
8dbd68a3d8 Wrap up this blog post 2022-11-15 17:45:46 -08:00
3d96dcf0c8 Add a blank rss_item.rss template as a catch-all for pages that should not be in the feed 2022-11-15 17:45:46 -08:00
df42fa88f0 Put - around $scheme declaration in atom_entry_metadata 2022-11-15 17:45:46 -08:00
e73040bb44 Do not escape .Content in the RSS feed templates 2022-11-15 17:45:46 -08:00
af23a386cf Move index templates up to layouts/ 2022-11-15 17:45:46 -08:00
a0994e75e9 Break up the RSS template into item templates, like I did for atom 2022-11-15 17:45:46 -08:00
8b2c0ecdb5 Rename the atom_entry templates with .atom suffix 2022-11-15 17:45:46 -08:00
cd85c44f6e OOPS! Add back <a> tags to the site menu 2022-11-15 17:45:46 -08:00
32665d45f6 Remove the back button from photo posts 2022-11-15 17:45:46 -08:00
09b72463e6 Clean up the site header template a little bit 2022-11-15 17:45:46 -08:00
6ef2172411 Convert the social menu to a config driven version 2022-11-15 17:45:46 -08:00
69c9588608 Generate a feed.atom too 2022-11-15 17:45:46 -08:00
96382b3231 Always include <content> in photos atom entry 2022-11-15 17:45:46 -08:00
bdfbcc634d Add alt text to the Harpswell post 2022-11-15 17:45:46 -08:00
5fb1070178 Add an inline <img> in the photo Atom entry 2022-11-15 17:45:46 -08:00
2a796299a5 Use the photo_thumbnail partial in the li_thumbnail_in_grid template 2022-11-15 17:45:46 -08:00
032fac4396 Clean up formatting of photo shortcode 2022-11-15 17:45:45 -08:00
05fec7a13c Remove <author> from the Atom entry metadata 2022-11-15 17:45:45 -08:00
7f12acf917 OOPS! Fix permalinks on photo pages 2022-11-15 17:45:45 -08:00
641fd8ccbf Add a set of templates to generate an Atom feed! 2022-11-15 17:45:45 -08:00
2c432932db Update the photo_thumbnail template to return the thumbnail itself, unmodified, if no changes are required 2022-11-15 17:45:45 -08:00
d85baf5d92 Reformat the head template a little bit 2022-11-15 17:45:45 -08:00
27833ba545 Get the Atom output type all set up
Atom feed is generated at feed.xml using the index.atom.xml template
2022-11-15 17:45:45 -08:00
21d7ced130 Add email to Author config 2022-11-15 17:45:45 -08:00
be6c69c9d6 Convert site config to YAML and move it to config/
Keep the old site config.toml around just in case.
2022-11-15 17:45:45 -08:00
e4d8ebcd58 Add photo thumbnail, category, series, and tags to RSS items 2022-11-15 17:45:45 -08:00
1745f46664 Add orrs-island photo and a bunch of metadata to some posts 2022-11-15 17:45:45 -08:00
3b1eee9e21 Clean up the development CSS styles a little bit 2022-11-15 17:45:45 -08:00
9def2ae7dd Remove the .post-content section around {{ .Content }} 2022-11-15 17:45:45 -08:00
f1d5b2ef58 Add the series to the content header 2022-11-15 17:45:45 -08:00
c74741f5c5 Move footer tags list to a partial so photos and blogs can use the same template
Simplify the blog post layout by removing the <article> tag
2022-11-15 17:45:45 -08:00
a186469c02 Add Harpswell Sunset post 2022-11-15 17:45:45 -08:00
51a9ad4c11 Add hugo-new-photo.py to create a photo post from a list of photos 2022-11-15 17:45:44 -08:00
033a780507 Use .HasShortcode instead of setting the "includes_" variables in .Page.Store 2022-11-15 17:45:44 -08:00
e2b454d7df Use .Page.Store instead of .Page.Scratch for conditional includes of support JS
I saw this on one of the Hugo documentation pages, so I copied it. I am not sure what the difference is.
2022-11-15 17:45:44 -08:00
6eaa667a7f Try to fix the debug EXIF info table -- I don't think this quite does it 2022-11-15 17:45:44 -08:00
3d28ef4d19 Let the photos section adapt to light/dark mode 2022-11-15 17:45:44 -08:00
acf249aea1 Add a shortcode to embed a photo post in a blog post 2022-11-15 17:45:44 -08:00
0a3fe90aa4 Redo the blog list layout to make it work a little better on narrow screens 2022-11-15 17:45:44 -08:00
fef99c888f Delete .gitmodules
This is leftover from a loooong time ago.
2022-11-15 17:45:44 -08:00
a56c431353 Remove the border from the site header when it's pinned to the top 2022-11-15 17:45:44 -08:00
0ece19281b Expand the theme colors to full 6-digit hex values 2022-11-15 17:45:44 -08:00
5cc42659f5 Two photos from Boston 2022-11-15 17:45:44 -08:00
d3f8024cfa Add images/orientation_angle partial that returns the angle (in degress) of the orientation from an image's Exif info 2022-11-15 17:45:44 -08:00
72f218b159 Specify clip-path along with -webkit-clip-path for .circular 2022-11-15 17:45:44 -08:00
7123b18c2b Photos page styles
- Create items in the grid for the month and year
- Use child selectors where possible
- fix the layout
2022-11-15 17:45:44 -08:00
6fdd80733e Clean up the styles of headings and paragraphs 2022-11-15 17:45:44 -08:00
4082880213 Remove a bunch of the reset styles 2022-11-15 17:45:44 -08:00
4151c912d8 Get thumbnails oriented the right way before resizing 2022-11-15 17:45:43 -08:00
5cd36cf08c Clean up the footer layout and styles 2022-11-15 17:45:43 -08:00
f3f2badae3 Clean up the header layout and styles 2022-11-15 17:45:43 -08:00
91db0b0e66 Use item-spacing for between the footer of a post and the content 2022-11-15 17:45:43 -08:00
34e3f4a1a3 Ignore *~ files 2022-11-15 17:45:43 -08:00
2be00db613 Make the about page not-a-draft 2022-11-15 17:45:43 -08:00
1f992e0e55 Fix up the home page; make the font size bigger; clean up the layout 2022-11-15 17:45:43 -08:00
37fb16f0f4 Add langs.title to mytilene photo 2022-11-15 17:45:43 -08:00
63bbe0e57c Default single.html template cleanup 2022-11-15 17:45:43 -08:00
e0fcfbaaa1 Bunch of misc photos template clean up 2022-11-15 17:45:43 -08:00
33192ddcfc Bring back line-height: 1 2022-11-15 17:45:43 -08:00
9e675b4724 Clean up some photos selectors -- use child selectors instead of descendant 2022-11-15 17:45:43 -08:00
92a9b60558 P5.js post: Wrap all the code blocks in figure shortcodes and fix the railroad diagrams 2022-11-15 17:45:43 -08:00
32f83443f4 Move the li_grid_with_date template to blog/ 2022-11-15 17:45:43 -08:00
f9543b6d85 Use JS to show/hide railroad diagrams based on page width (thanks @hober!) 2022-11-15 17:45:43 -08:00
2f6e78fa91 Group photos posts by month and year 2022-11-15 17:45:43 -08:00
13c3ad7a9e Add a item template for photos in a grid 2022-11-15 17:45:43 -08:00
6a750eafb1 Photos WIP 2022-11-15 17:45:43 -08:00
f74c647fb3 Add a cute backdrop filter to the header 2022-11-15 17:45:43 -08:00
9e262e3581 Set line-height of figure to 0 so it stops adding 1px of extra space to the bottoms!! 2022-11-15 17:45:43 -08:00
87be371e80 Add meta theme-color tags to <head> 2022-11-15 17:45:42 -08:00
4488ffbf97 Add a .list class to <main> when there are .Pages 2022-11-15 17:45:42 -08:00
c733b6d1aa Use relative URLs in the baseof template 2022-11-15 17:45:42 -08:00
17af243a96 Rework the background-color and color CSS vars 2022-11-15 17:45:42 -08:00
02a66d9f28 Increase line height of body text to 1.5 2022-11-15 17:45:42 -08:00
eee6d0e7a8 Add a weight to the photos menu item 2022-11-15 17:45:42 -08:00
92fe172d1a Move all the images to git LFS 2022-11-15 17:45:42 -08:00
40fbc2c881 Fix up the CSS and photos list templates 2022-11-15 17:45:41 -08:00
66240852ed Some photos from Greece 2022-11-15 17:45:40 -08:00
565fd11718 First cut at a photos site 2022-11-15 17:45:40 -08:00
d688cb8203 Ignore log files 2022-11-15 17:45:40 -08:00
51fc76bf26 Some changes to the VSCode workspace 2022-11-15 17:45:40 -08:00
ad7db655d6 Work on a site logo with CSS and HTML 2022-11-15 17:45:40 -08:00
b17919fbba WIP Hugo Dictionary API post 2022-10-15 10:20:03 -07:00
92 changed files with 1475 additions and 458 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text

2
.gitignore vendored
View file

@ -1,4 +1,6 @@
public/
resources/
.hugo_build.lock
*.log
*.orig
*~

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "themes/paper"]
path = themes/paper
url = ssh://git@github.com:erynofwales/hugo-paper.git

View file

@ -30,6 +30,11 @@ name = 'Eryn Wells'
name = 'Blog'
url = '/blog/'
weight = 10
[[menu.main]]
identifier = 'photos'
name = 'Photos'
url = '/photos/'
weight = 20
[[menu.main]]
identifier = 'about'
name = 'About'
@ -53,6 +58,7 @@ description = 'Home page of Eryn Wells'
[permalinks]
blog = 'blog/:year/:month/:slug/'
photos = 'photos/:year/:month/:slug/'
[taxonomies]
category = 'categories'

View file

@ -0,0 +1,2 @@
name: Eryn Wells
email: eryn@erynwells.me

View file

@ -0,0 +1,4 @@
baseURL: https://erynwells.me/
languageCode: en-US
title: Erynwells.me
defaultContentLanguage: en

View file

@ -0,0 +1,4 @@
en:
weight: 1
es:
weight: 2

View file

@ -0,0 +1,5 @@
highlight:
anchorLineNos: true
lineNos: true
lineNumbersInTable: false
noClasses: false

View file

@ -0,0 +1,6 @@
application/rss+xml:
delimiter: .
suffixes: [rss]
application/atom+xml:
delimiter: .
suffixes: [atom, xml]

39
config/_default/menu.yaml Normal file
View file

@ -0,0 +1,39 @@
main:
- identifier: blog
name: Blog
url: /blog/
weight: 10
- identifier: photos
name: Photos
url: /photos/
weight: 20
- identifier: about
name: About
url: /about/
weight: 30
social:
- identifier: twitter
name: Twitter
url: https://twitter.com/erynofwales
weight: 10
params:
shortName: tw
- identifier: github
name: Github
url: https://github.com/erynofwales
weight: 20
params:
shortName: gh
- identifier: instagram
name: Instagram
url: https://instagram.com/erynofwales
weight: 30
params:
shortName: ig
- identifier: feed
name: Feed
url: /feed.atom
weight: 40
params:
shortName: feed
targetBlank: false

View file

@ -0,0 +1,8 @@
RSS:
mediatype: application/rss+xml
baseName: feed
suffixes: [rss]
Atom:
mediatype: application/atom+xml
baseName: feed
suffixes: [atom, xml]

View file

@ -0,0 +1,2 @@
home: [HTML, Atom, RSS]
page: [HTML, JSON]

View file

@ -0,0 +1,4 @@
twitter: erynofwales
github: erynofwales
instagram: erynofwales
description: Home page of Eryn Rachel Wells

View file

@ -0,0 +1,2 @@
blog: blog/:year/:month/:slug/
photos: photos/:year/:month/:slug/

View file

@ -0,0 +1,2 @@
twitter:
enableDNT: true

View file

@ -0,0 +1,2 @@
twitter:
disableInlineCSS: true

View file

@ -0,0 +1,4 @@
category: categories
location: locations
series: series
tag: tags

View file

@ -1,5 +1,6 @@
---
title: "Hola! 👋🏻"
draft: false
---
Me llamo Eryn. Mis pronombres son [ella/ella][p]. Esta es me página personal. Bienvenide.

View file

@ -1,7 +1,7 @@
---
title: "Hi! 👋🏻"
date: 2022-09-03T12:14:32-07:00
draft: true
draft: false
resources:
- name: me
src: me.jpeg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 132 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 132 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

After

Width:  |  Height:  |  Size: 132 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

After

Width:  |  Height:  |  Size: 131 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 KiB

After

Width:  |  Height:  |  Size: 131 B

Before After
Before After

View file

@ -23,11 +23,13 @@ visualizations. By the end, we'll have something like this:
HTML has the ability to [embed audio][mdn-audio-tag] in a page with the
`<audio>` tag. This one declares a single MP3 file as a source.
{{< figures/code >}}
```html
<audio id="amen">
<source src="amen.mp3" type="audio/mpeg">
</audio>
```
{{< /figures/code >}}
In this form, the `<audio>` element doesn't do anything except declare some
audio that can be played. It's invisible and the user can't interact with it or
@ -47,6 +49,7 @@ destinations could be your computer's speakers or a file.
Here's the entire code snippet that sets up the audio processing I need for the
sketch:
{{< figures/code >}}
```js {linenostart=2}
let analyzerNode = null;
let samples = null;
@ -67,6 +70,7 @@ let audioContext = (() => {
return audioContext;
})();
```
{{< /figures/code >}}
The [`AudioContext`][mdn-audio-context] is the object that encapsulates the
entire node graph. On line 10, I create a new `AudioContext`.
@ -85,19 +89,20 @@ the audio context's `destination` node that routes to the computer's speakers.
Our audio processing graph looks like this:
{{< figures/railroad id="audioContextDiagram" >}}
return rr.Diagram(
rr.Sequence(
rr.Terminal("<audio>"),
rr.Terminal("Analyzer"),
rr.Terminal("destination")));
{{< /figures/railroad >}}
{{< figures/railroad id="audioContextDiagram" class="narrow-only" >}}
{{< scripts/railroad >}}
return rr.Diagram(
rr.Sequence(
rr.Terminal("<audio>"),
rr.Terminal("Analyzer"),
rr.Terminal("destination")));
{{< /scripts/railroad >}}
{{< scripts/railroad narrow=1 >}}
return rr.Diagram(
rr.Stack(
rr.Terminal("<audio>"),
rr.Terminal("Analyzer"),
rr.Terminal("destination")));
{{< /scripts/railroad >}}
{{< /figures/railroad >}}
By itself the AudioContext doesn't actually play any audio. I'll tackle that
@ -109,6 +114,7 @@ Next up is starting playback. The following snippet creates a Play button using
P5.js's DOM manipulation API, and hooks up the button's `click` event to start
and stop playback.
{{< figures/code >}}
```js {linenostart=29}
const playPauseButton = p.createButton('Play');
playPauseButton.position(10, 10);
@ -131,6 +137,7 @@ playPauseButtonElement.addEventListener('click', function() {
}
});
```
{{< /figures/code >}}
Something I found odd while working with these audio components is there isn't a
way to ask any of them if audio is playing back at any given moment. Instead it
@ -147,12 +154,14 @@ The last bit of playback state tracking to do is to listen for when playback
ends because it reached the end of the audio file. I did that with the `ended`
event:
{{< figures/code >}}
```js {linenostart=53}
audioElement.addEventListener('ended', function() {
playPauseButtonElement.dataset.playing = 'false';
playPauseButtonElement.innerHTML = '<span>Play</span>';
}, false);
```
{{< /figures/code >}}
This handler resets the `playing` flag and the label of the button.
@ -160,6 +169,7 @@ This handler resets the `playing` flag and the label of the button.
Now it's time to draw some waveforms! The main part of a P5 sketch is the `draw` method. Here's mine:
{{< figures/code >}}
```js {linenostart=57}
const amplitude = p.height / 2;
const axis = p.height / 2;
@ -184,12 +194,15 @@ for (let i = 0; i < samples.length; i++) {
p.point(i, axis + amplitude * sampleValue);
}
```
{{< /figures/code >}}
The most interesting part of this function starts at line 66 where we get an array of samples from the analyzer node. The `samples` variable is a JavaScript `Float32Array`, with one element for each pixel of width.
{{< figures/code >}}
```js {linenostart=30}
samples = new Float32Array(p.width);
```
{{< /figures/code >}}
Once the sample data is populated from the analyzer, we can render them by
plotting them along the X axis, scaling them to the height of the sketch.

View file

@ -0,0 +1,152 @@
---
title: "Hugo's Dictionary API"
date: 2022-10-13T10:19:02-07:00
draft: true
categories: ["Tech"]
tags: ["Hugo", "Web", "API Design"]
series: "Erynwells.me Development"
---
Hugo's templating system has support for dictionaries. Unfortunately the API for
working with them is, frankly, awful. While working on developing some new
templates for this site, I had to figure out how to build up dictionary data
structures and it took me a _long_ time to figure out how to do some basic
operations with them.
Here's a quick summary of what I found.
## Creating Dictionaries
The function to create a dictionary is called [`dict`][dict] and it takes a
variable number of arguments that alternate between keys and values. It reminds
me of this [bizarre and backwards NSDictionary API][nsdictionary-init] in Apple's
Foundation framework. Keys must be strings (or string slices) and values can be
anything. So this:
{{< figures/code >}}
```go-html-template
{{ $d := dict "a" 1 "b" 2 "c" 3 }}
```
{{< /figures/code >}}
creates a structure that looks like this JSON object:
{{< figures/code >}}
```json
{ "a": 1, "b": 2, "c": 3 }
```
{{< /figures/code >}}
You can also create an empty dictionary by calling `dict` with no arguments.
{{< figures/code >}}
```go-html-template
{{ $d := dict }}
```
{{< /figures/code >}}
## Accessing Keys and Values
Statically, you can get a single item in a dictionary with dot syntax. Below,
`$item` will get the value 1.
{{< figures/code >}}
```go-html-template
{{ $item := (dict "a" 1 "b" 2 "c" 3).a }}
```
{{< /figures/code >}}
If you want to get a value with a key you get at render time, you can use the
[`index`][index] function. In the snippet below, `$item` will get the value of
`"b"`, which is 2.
{{< figures/code >}}
```go-html-template
{{ $key := "b" }}
{{ $item := index $key (dict "a" 1 "b" 2 "c" 3) }}
```
{{< /figures/code >}}
`index` doesn't make much sense to me as a verb for accessing values in a
dictionary. It sounds more like an array function, and indeed it's the function
that gives you access to items in arrays. I would like to see another function
with a more dictionary-sounding name, like `get` or `value` or `item`, even if
it were just an alias for `index` underneath.
## Adding Items to a Dictionary
This is a bit complex because, as far as I can tell, dictionaries are immutable.
So, if you want to update a dictionary, you need to combine two dictionaries and
then save it back to the original variable. The [`merge`][merge] function does
that. Here's a snippet:
{{< figures/code >}}
```go-html-template
{{ $d := dict "a" 1 "b" 2 "c" 3 }}
{{ $d = merge $d (dict "b" 4) }}
{{ $item = index "b" $d }}
```
{{< /figures/code >}}
`merge` takes a variable number of arguments, and merges dictionaries left to
right. So, items in dictionaries later in the argument list will override items
in dictionaries earlier in the list.
Just to underscore, you have to set the update dictionary back to the original
variable to complete the update, hence the `$d = ...`.
All that is to say: at the end of that snippet, `$item` will get the value 4.
## A Complex Example: A Dictionary of Arrays
For the previously mentioned template changes I was making, I was updating the
`terms` template for my category taxonomy. For each category, I wanted to show
one section per tag, and a list of all the posts with that tag underneath.
My categories are high level groups like "Tech," "Music," and "Travel." Tags are
more specific topics for the post like "Web" or "Compositions." Pages only ever
have one category but they can have multiple tags.
A `terms` template lets you access an array of terms, and the pages associated
with those terms. You can access the tags attached to a page with the
`.GetTerms` function. Here's what I did, and then I'll talk through it:
{{< figures/code >}}
```go-html-template
{{- $pagesByTag := dict -}}
{{- range $page := .Pages -}}
{{- range $tag := .GetTerms "tags" -}}
{{- $tagName := $tag.Name -}}
{{- if not (in $pagesByTag $tagName) -}}
{{- $pagesByTag = merge $pagesByTag
(dict $tagName (slice $page)) -}}
{{- else -}}
{{- $pagesForTag := index $pagesByTag $tagName -}}
{{- $pagesForTag = $pagesForTag | append $page -}}
{{- $pagesByTag = merge $pagesByTag
(dict $tagName $pagesForTag) -}}
{{- end -}}
{{- end -}}
{{- end -}}
```
{{< /figures/code >}}
`$pagesByTag` is my empty dictionary. It will hold tag names as keys, each
pointing to a slice (array) of page objects. For each page, I get its list of
tags. For each tag, I check `$pagesByTag` to see if it already has a key/value
pair for that tag. If not, I create a new entry in `$pagesByTag` with `merge`.
If it does already, I get the slice for that tag with `index`, add the Page to
the slice with `append`, and then merge the updated slice back into
`$pagesByTag` with `merge`.
It's not too bad once it's all spelled out, but it does feel like more work than
it should take for such simple operations.
I think this API could be improved substantially with some new functions that
operate specifically on dictionaries and that have clear names that describe
what they do.
[dict]: https://gohugo.io/functions/dict/
[index]: https://gohugo.io/functions/index-function/
[merge]: https://gohugo.io/functions/merge/
[nsdictionary-init]: https://developer.apple.com/documentation/foundation/nsdictionary/1574181-dictionarywithobjectsandkeys?language=objc

View file

@ -1,24 +1,13 @@
:root {
--post-item-highlight-color: #efefef;
--tag-foreground-color: rgb(var(--super-dk-gray));
--tag-background-color: rgb(var(--super-lt-gray));
--tag-spacer-foreground-color: rgb(var(--super-dk-gray));
--tag-hover-background-color: rgb(var(--sub-lt-gray));
}
@media (prefers-color-scheme: dark) {
:root {
--post-item-highlight-color: #121212;
--tag-foreground-color: rgb(var(--sub-lt-gray));
--tag-background-color: rgb(var(--dk-gray));
--tag-spacer-foreground-color: rgb(var(--super-dk-gray));
--tag-hover-background-color: rgb(var(--super-dk-gray));
--tag-hover-foreground-color: rgb(var(--mid-gray));
}
}
.post-content > .highlight { margin-block-end: var(--body-item-spacing); }
.blog > .highlight { margin-block-end: var(--body-item-spacing); }
.post-nav {
align-items: baseline;
@ -31,57 +20,63 @@
}
.post-single footer {
margin-block-start: 3.5rem;
margin-block-start: var(--body-item-spacing);
}
.blog .tags {
display: flex;
padding-inline-start: 0;
.blog.list > ul {
list-style: none;
}
.blog .tags li {
background-color: var(--tag-background-color);
color: var(--tag-foreground-color);
border-radius: 4px;
display: inline-block;
font-size: 75%;
letter-spacing: 1px;
.blog.list > ul > li {
align-items: baseline;
border-radius: 6px;
display: grid;
gap: 1rem;
grid-template-columns: minmax(min-content, 10vh) minmax(min-content, 3vh) auto max-content;
margin-block-end: 0.25rem;
transition: background-color 0.25s;
}
.blog .tags li:hover {
background-color: var(--tag-hover-background-color);
.blog.list > ul > li:hover {
background-color: var(--post-item-highlight-color);
}
.blog .tags li a {
.blog.list > ul > li > a {
color: inherit;
display: block;
height: 100%;
width: 100%;
padding: 0.6rem 1rem;
}
.blog .tags li a:hover {
color: var(--tag-hover-foreground-color);
.blog.list > ul > li > a:hover {
text-decoration: none;
}
.blog .tags li + li { margin-inline-start: 1rem; }
.blog .tags li.chevron + li { margin-inline-start: 0 }
.blog .tags .chevron {
align-items: center;
border: 0;
border-radius: 0;
color: var(--tag-spacer-foreground-color);
background-color: inherit;
display: flex;
justify-content: center;
margin-inline-start: 0;
padding-inline-start: 1px;
width: 2rem;
.blog.list > ul > li > .draft {
align-self: center;
}
.blog .tags .chevron:hover {
color: var(--tag-background-color);
background-color: inherit;
@supports (grid-template-columns: subgrid) {
.blog.list {
display: grid;
gap: 1rem;
grid-template-columns: minmax(min-content, 10vh) minmax(min-content, 3vh) auto max-content;
}
.blog.list > ul {
display: grid;
row-gap: 0.25rem;
grid-column: 1 / -1;
grid-template-columns: subgrid;
list-style: none;
}
.blog.list > ul > li {
display: grid;
grid-column: 1 / -1;
grid-template-columns: subgrid;
margin-block-end: 0;
}
.blog.list > h5 {
grid-column: 1 / -1;
margin-block: 1rem 0;
}
.blog.list > h5:first-child {
margin-block-start: 0;
}
}
.blog.list > ul > li > :first-child {
text-align: end;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

After

Width:  |  Height:  |  Size: 131 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 131 B

Before After
Before After

View file

@ -1,5 +1,6 @@
:root {
--animation-offset: 6px;
--font-size-max: 12px;
}
body {
@ -16,10 +17,14 @@ h1 {
margin: 0;
padding: 0;
}
@media (max-width: 450px) {
@media (max-width: 599px) {
h1 { font-size: 4.25rem; }
}
html {
font-size: clamp(var(--font-size-min), 2.5vw, var(--font-size-max));
}
main {
margin-block-start: 10vh;
width: inherit;
@ -29,7 +34,7 @@ main .grid {
align-items: baseline;
display: grid;
gap: 0 2rem;
grid-template-columns: min-content 200px;
grid-template-columns: minmax(min-content, 1fr) minmax(min-content, 2fr);
grid-template-rows: repeat(2, max-content);
grid-template-areas:
"title blurb"
@ -37,7 +42,7 @@ main .grid {
padding-inline: 1em;
width: min-content;
}
@media (max-width: 450px) {
@media (max-width: 599px) {
main .grid {
gap: 1rem 0;
grid-template-columns: 1fr;
@ -54,37 +59,48 @@ main h1 {
letter-spacing: 0.025em;
text-align: end;
}
@media (max-width: 450px) {
@media (max-width: 599px) {
main h1 { text-align: center; }
}
main nav {
nav {
align-items: baseline;
display: flex;
list-style: none;
text-transform: lowercase;
}
main p {
nav > li {
margin-inline-start: 0.5em;
}
nav > li:first-of-type {
margin-inline-start: 0;
}
p {
margin: 0;
}
#title { grid-area: title; }
h1 { grid-area: title; }
#content { grid-area: blurb }
#nav { grid-area: nav }
nav { grid-area: nav }
#title > *, #content > *, #nav > * { position: relative; }
h1, #content > p, nav { position: relative; }
#title > * {
h1 {
animation: left-fade-in var(--transition-duration) ease-in-out;
}
@media (max-width: 450px) {
#title > * {
@media (max-width: 599px) {
h1 {
animation: top-fade-in var(--transition-duration) ease-in-out;
}
}
#content > *, #nav > * {
#content > p, nav {
animation: right-fade-in var(--transition-duration) ease-in-out;
}
@media (max-width: 450px) {
#content > *, #nav > * {
@media (max-width: 599px) {
#content > p, nav {
animation: bottom-fade-in var(--transition-duration) ease-in-out;
}
}

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5cd94f1b871fbe127d65f9ff02a2ce5f431dfac370c77f07e2fc7d04d72dddd7
size 1916134

View file

@ -0,0 +1,6 @@
---
title: Columns at the Library of Hadrian
date: 2022-07-30T13:30:07+02:00
draft: false
series: Greece
---

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6dcd74651a38a9fb740190cff140c436f5967429262a51c95974d0a3d5afe0bb
size 5319563

View file

@ -0,0 +1,6 @@
---
title: Space
date: 2022-07-29T20:27:01+02:00
draft: false
series: Greece
---

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fa41885ab32ac9dd4a59c91c7f74820787f20426f6b80fd88aa8ff8266c91413
size 1599817

View file

@ -0,0 +1,7 @@
---
title: Μήθυμνα
slug: mithymna
date: 2022-08-03T16:42:22+02:00
draft: false
series: Greece
---

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0ad206dae43892251f80714d015ad546f5585b99acc70eab5e34578d09ef250c
size 2782093

View file

@ -0,0 +1,9 @@
---
title: Μυτιλήνη
slug: mytilene
date: 2022-08-05T13:09:45+02:00
draft: false
series: Greece
langs:
title: gr
---

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dd90a94609c9cebb8ee975734a645ce9b2c4d85dd07c2da45b00b7908a24e13d
size 3952113

View file

@ -0,0 +1,19 @@
---
title: Ψάπφω Ερεσού
slug: sappho-the-eresian
date: 2022-08-04T15:32:20+02:00
draft: false
series: Greece
locations:
- Skala Eressos, Lesbos, Greece
- Lesbos
- Greece
- Σκαλα Ερεσού
- Λεσβος
- Ελλάδα
---
I fell in love with this piece of street art depicting Sappho as a modern woman
by [@muckrock][muckrock] in Σκάλα ερεσού.
[muckrock]: https://www.instagram.com/muckrock/

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5155f190e4200fcadd9103d0d548d854151dff2358b81444d931d4210e6419c
size 4298104

View file

@ -0,0 +1,14 @@
---
title: "Gardner Museum"
date: 2022-10-24T16:07:00-04:00
draft: false
series: "Spruce Goose‼"
tags: ["Museums", "Gardens"]
locations:
- Boston, MA
- Massachusettes
- New England
- United States
---
Isabella Stewart Gardner was an obsecenely wealthy lady with a very strange and cool house.

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:365b6fdbfb2ff929e008bdc4b70c58820ecd00f255ebeb5956b4edc3f42fe4b0
size 3475571

View file

@ -0,0 +1,20 @@
---
title: Harpswell Sunset
date: 2022-10-28T14:19:58-07:00
draft: false
series: "Spruce Goose‼"
tags: ["Sunsets", "Ocean", "Friends"]
locations:
- Harpswell, ME
- Maine
- New England
- United States
resources:
- name: sunset
alt: "The sun sets over an inlet on Casco Bay. In the foreground is a fence, with a tidal mudflat beyond. There are several people out on the mudflat. There's another strip of land in the far distance, and a few boats moored to bouys in the water."
src: IMG_0380.jpeg
---
Maine did not disappoint on our last full day in Harpswell. The sunset was
gorgeous, and a bunch of us walk out on the mudflats to check out the literal
thousands of snails, barnicles, and seaweed.

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6f1202980dc20a208999fb83138ff6470940f71a4c62ca056ad15705b007c582
size 3641161

View file

@ -0,0 +1,19 @@
---
title: "Orrs Island"
date: 2022-10-28T11:21:06-07:00
draft: false
series: "Spruce Goose‼"
tags: ["Hiking", "Ocean"]
locations:
- Harpswell, ME
- Maine
- New England
- United States
---
We took a short hike on Orrs Island, near where we were staying. It was a small
loop trail in the ominously named "Devil's Back" trail area that wasn't
particularly strenous apart from the soft ground and many rocks and roots we had
to hike over.
We did have to do the limbo under a fallen tree though.

View file

@ -0,0 +1,7 @@
---
title: "Scituate Light"
date: 2022-10-22T12:41:00-04:00
draft: false
---
Saying hello to the Atlantic in Scituate, MA 🌊👋🏻

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7d319bf7f9f3942206f44f5a095eaf3b6df237d58c47c7458e051da5566a063f
size 3881662

3
content/photos/_index.md Normal file
View file

@ -0,0 +1,3 @@
---
title: Photos
---

166
content/photos/photos.css Normal file
View file

@ -0,0 +1,166 @@
:root {
--date-item-background-color: rgb(var(--lt-gray));
--photo-params-background-color: rgb(var(--lt-gray));
--photo-params-container-background-color: rgb(var(--super-lt-gray));
--photo-params-color: rgb(var(--sub-dk-gray));
--photo-params-border-color: rgb(var(--super-lt-gray));
}
@media (prefers-color-scheme: dark) {
:root {
--date-item-background-color: rgb(var(--dk-gray));
--photo-params-background-color: rgb(var(--dk-gray));
--photo-params-container-background-color: rgb(var(--sub-dk-gray));
--photo-params-color: rgb(var(--super-lt-gray));
--photo-params-border-color: rgb(var(--sub-dk-gray));
}
}
.photos.list {
box-sizing: border-box;
max-width: none;
padding: 0 var(--body-item-spacing);
width: 100%;
}
.photos.list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 4px;
}
.photos.page > nav {
margin-block-end: var(--body-item-spacing);
}
.photos.list > a {
display: block;
line-height: 0;
}
.photos.list > a > img {
border-radius: 3px;
image-orientation: from-image;
}
.photos.list > div {
background-color: var(--date-item-background-color);
border-radius: 3px;
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
.photos.list > div > h6 {
display: block;
font-size: 5rem;
margin: 0 auto;
letter-spacing: 0;
}
.photos.list > div > h6 > span {
text-align: end;
width: min-content;
position: relative;
display: inline-block;
padding-inline-end: 20px;
}
.photos.list > div > h6 > span::after {
top: 6px;
right: 0px;
position: absolute;
display: block;
content: "⏵︎";
font-size: 80%;
}
@media (max-width: calc(24px + 400px + 4px)) {
.photos.list > div > h6 > span {
width: max-content;
padding-block: calc(0.25 * var(--body-item-spacing));
}
.photos.list > div > h6 > span::after {
content: "";
}
}
.photo-params {
width: 100%;
}
.photo-params .container {
display: block;
background-color: var(--photo-params-container-background-color);
border-radius: 10px;
margin: var(--body-item-spacing) auto;
padding: calc(var(--body-item-spacing) / 2);
width: 66%;
}
.photo-params table {
background-color: var(--photo-params-background-color);
color: var(--photo-params-color);
border-collapse: collapse;
border-radius: 6px;
table-layout: fixed;
text-align: center;
width: 100%;
}
.photo-params thead td {
border-bottom: 1px solid var(--photo-params-border-color);
font-size: 80%;
font-weight: bold;
}
.photo-params tr.exposure-attributes td {
border-top: 1px solid var(--photo-params-border-color);
border-left: 1px solid var(--photo-params-border-color);
}
.photo-params tr.exposure-attributes td:first-child {
border-left: none;
}
.photo-params td {
font-size: 75%;
padding: 1rem;
}
.photo-params td:last-child {
text-align: end;
}
.photo-params td:first-child {
text-align: start;
}
.photo-params .make-model {
font-weight: bold;
}
.photo-params .size {
border-left: 0;
}
.photo-params .location {
border-right: 0;
}
.photo-params.debug thead {
font-size: 2rem;
font-weight: bold;
border-bottom: 2px solid rgb(var(--dk-gray));
}
.photo-params.debug {
width: 100%;
}
.photo-params.debug td {
border: 1px solid var(--photo-params-border-color);
overflow: scroll;
vertical-align: top;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 132 B

Before After
Before After

View file

@ -4,5 +4,7 @@
"path": "."
}
],
"settings": {}
}
"settings": {
"makefile.extensionOutputFolder": "./.vscode"
}
}

76
hugo-new-photo.py Executable file
View file

@ -0,0 +1,76 @@
#!env/bin/python3
# Eryn Wells <eryn@erynwells.me>
'''
New script.
'''
import argparse
import datetime
import os.path
import shutil
import subprocess
from PIL import Image
from PIL.ExifTags import TAGS
PHOTOS_CONTENT_DIR = 'content/photos'
def parse_args(argv, *a, **kw):
parser = argparse.ArgumentParser(*a, **kw)
parser.add_argument('-n', '--dry-run', action='store_true')
parser.add_argument('-t', '--title', type=str)
parser.add_argument('photos', nargs='+')
args = parser.parse_args(argv)
return args
def main(argv):
args = parse_args(argv[1:], prog=argv[0])
earliest_exif_date = None
for photo in args.photos:
try:
image = Image.open(photo)
except IOError:
pass
raw_exif = image._getexif()
friendly_exif = {TAGS[k]: v for k, v in raw_exif.items() if k in TAGS}
date_string = f'{friendly_exif["DateTime"]} {friendly_exif["OffsetTime"]}'
exif_date = datetime.datetime.strptime(date_string, '%Y:%m:%d %H:%M:%S %z')
print(f'{photo} -> {exif_date.isoformat()}')
if not earliest_exif_date or exif_date < earliest_exif_date:
earliest_exif_date = exif_date
year = earliest_exif_date.year
month = earliest_exif_date.month
name = args.title if args.title else os.path.splitext(os.path.basename(photo))[0]
post_path = os.path.join(PHOTOS_CONTENT_DIR, str(year), str(month), name)
try:
hugo_command = ['hugo', 'new', '--clock', earliest_exif_date.isoformat(), post_path]
if not args.dry_run:
result = subprocess.run(hugo_command)
result.check_returncode()
else:
print(' '.join(hugo_command))
except subprocess.CalledProcessError:
print(f'Failed to create new Hugo post', file=sys.stderr)
return -1
for photo in args.photos:
print(f'Copy {photo} -> {post_path}')
try:
if not args.dry_run:
shutil.copy(photo, post_path)
except IOError:
print(f'Failed to copy {photo}', file=sys.stderr)
return -2
if __name__ == '__main__':
import sys
result = main(sys.argv)
sys.exit(0 if not result else result)

View file

@ -5,7 +5,7 @@
<body>
{{ block "body" . }}
{{ block "header" . }}{{ end }}
<main class="{{ .Type }}">
<main class="{{ .Type }} {{ .Kind }}{{ if gt (len .Pages) 0 }} list{{ end }}">
{{ block "main" . }}{{ end }}
</main>
{{ partial "development/page_info.html" . }}
@ -16,35 +16,37 @@
<style>
@font-face {
font-family: "Museo_Slab";
src: url("{{ `/fonts/Museo_Slab_500.woff2` | absURL }}") format("woff2"),
url("{{ `/fonts/Museo_Slab_500.woff` | absURL }}") format("woff");
src: url("{{ `/fonts/Museo_Slab_500.woff2` | relURL }}") format("woff2"),
url("{{ `/fonts/Museo_Slab_500.woff` | relURL }}") format("woff");
font-weight: normal;
font-style: normal;
}
</style>
<link rel="stylesheet" as="style" href="{{ `/styles/root.css` | absURL }}">
{{ if not hugo.IsProduction }}
<link rel="stylesheet" as="style" href="/styles/development.css">
{{ end }}
{{ block "styles" . }}{{ end }}
<link rel="stylesheet" as="style" href="{{ `/styles/root.css` | relURL }}">
{{- if not hugo.IsProduction -}}
<link rel="stylesheet" as="style" href="{{ `/styles/development.css` | relURL }}">
{{- end -}}
{{- $includedCSS := slice -}}
{{- if not .FirstSection.IsHome -}}
{{- range .FirstSection.Resources.Match "*.css" -}}
{{- if not (in $includedCSS .Permalink) -}}
{{- $includedCSS = $includedCSS | append .Permalink -}}
<link rel="stylesheet" as="style" href="{{ .Permalink }}">
<link rel="stylesheet" as="style" href="{{ .RelPermalink }}">
{{- end -}}
{{- end -}}
{{- end -}}
{{- range .Resources.Match "*.css" -}}
{{- if not (in $includedCSS .Permalink) -}}
{{- $includedCSS = $includedCSS | append .Permalink -}}
<link rel="stylesheet" as="style" href="{{ .Permalink }}">
<link rel="stylesheet" as="style" href="{{ .RelPermalink }}">
{{- end -}}
{{- end -}}
{{ block "styles" . }}{{ end }}
{{ block "scripts" . }}{{ end }}
<script src="{{ `scripts/site.js` | absURL }}"></script>
<script src="{{ `scripts/site.js` | relURL }}"></script>
</html>

View file

@ -1,22 +0,0 @@
{{ define "main" }}
<div class="platter">
<div class="grid">
<div id="title">
<h1 class="site">{{ .Title }}</h1>
</div>
<div id="content">
{{ .Content }}
</div>
{{ with site.Menus.main }}
{{ $url := $.RelPermalink }}
<div id="nav">
<nav class="site bulleted">
{{ range . }}
<li><a class="{{ if eq .URL $url }}active{{ end }}" href="{{ .URL }}"><span>{{ .Name }}</span></a></li>
{{ end }}
</nav>
</div>
{{ end }}
</div>
</div>
{{ end }}

View file

@ -1,41 +0,0 @@
{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- 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>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.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 $pages }}
{{ if ne .Params.rss_ignore true }}
<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>{{ .Content | html }}</description>
</item>
{{ end }}
{{ end }}
</channel>
</rss>

View file

@ -0,0 +1,4 @@
{{/*
Intentionally empty to stop pages that shouldn't be included in the RSS feed
from being rendered with the default RSS template.
*/}}

View file

@ -3,31 +3,31 @@
{{ end }}
{{ define "main" }}
{{ partial "single_main.html" . }}
{{ partial "single_main.html" . }}
{{ end }}
{{ define "styles" }}
{{ if .Page.Scratch.Get "includes_railroad_diagram" }}
<link rel="preload stylesheet" as="style" href="{{ `styles/railroad.css` | absURL }}">
{{ end }}
{{- if .HasShortcode "figures/railroad" -}}
<link rel="preload stylesheet" as="style" href="{{ `styles/railroad.css` | absURL }}">
{{- end -}}
<link rel="preload stylesheet" as="style" href="{{ `styles/monokai.css` | absURL }}">
{{ end }}
{{ define "scripts" }}
{{ if and .IsPage (.Page.Scratch.Get "includes_railroad_diagram") }}
<script defer type="module" src="{{ `scripts/railroad.js` | absURL }}"></script>
<script defer type="module" src="{{ `scripts/railroad-utils.js` | absURL }}"></script>
{{ end }}
{{- if .HasShortcode "figures/railroad" -}}
<script defer type="module" src="{{ `scripts/railroad.js` | absURL }}"></script>
<script defer type="module" src="{{ `scripts/railroad-utils.js` | absURL }}"></script>
{{- end -}}
{{ if .Page.Scratch.Get "includes_p5_sketch" }}
<script defer src="{{ `scripts/p5-1.4.1.min.js` | absURL }}"></script>
<script defer src="{{ `scripts/sketch-utils.js` | absURL }}"></script>
{{ end }}
{{- if .HasShortcode "figures/p5" -}}
<script defer src="{{ `scripts/p5-1.4.1.min.js` | absURL }}"></script>
<script defer src="{{ `scripts/sketch-utils.js` | absURL }}"></script>
{{- end -}}
{{ range $script := .Resources.Match "*.js" }}
{{ $isModule := default true $script.Params.is_module }}
<script defer {{ if $isModule }}type="module"{{ end }} src="{{ $script.Permalink | relURL }}"></script>
{{ end }}
{{- range $script := .Resources.Match "*.js" -}}
{{- $isModule := default true $script.Params.is_module -}}
<script defer {{ if $isModule }}type="module"{{ end }} src="{{ $script.Permalink | relURL }}"></script>
{{- end -}}
{{ end }}
{{ define "footer" }}

View file

@ -0,0 +1,6 @@
<entry>
{{ partial "atom_entry_metadata.xml" . }}
{{- if .Content -}}
{{ `<content type="html"><![CDATA[` | safeHTML }}{{ .Content }}]]></content>
{{- end -}}
</entry>

View file

@ -1,8 +1,6 @@
<li>
<time class="nobreak" datetime="{{ .Date | time.Format "2006-01-02" }}">{{ .Date | time.Format "January" }}</time>
<time class="nobreak" datetime="{{ .Date | time.Format "2006-01-02" }}">{{ .Date | time.Format "2" }}</time>
<div class="title">
<a href="{{ .Permalink }}">{{ .Title }}</a>
{{ partial "development/draft_tag.html" . }}
</div>
<a href="{{ .Permalink }}">{{ .Title }}</a>
{{ partial "development/draft_tag.html" . }}
</li>

View file

@ -3,18 +3,16 @@
{{ end }}
{{ define "main" }}
<section id="by-date">
{{- range .Pages.ByDate.GroupByDate "2006" -}}
<h6>{{ .Key | title }}</h6>
<ul class="post-list">
{{- range .Pages -}}
{{- if or (not .Draft) (not hugo.IsProduction) -}}
{{- .Render "li_grid_with_date" -}}
{{- end -}}
{{- range .Pages.ByDate.GroupByDate "2006" -}}
<h5>{{ .Key | title }}</h5>
<ul>
{{- range .Pages -}}
{{- if or (not .Draft) (not hugo.IsProduction) -}}
{{- .Render "li_grid_with_date" -}}
{{- end -}}
</ul>
{{- end -}}
</section>
</ul>
{{- end -}}
{{ end }}
{{ define "footer" }}

View file

@ -0,0 +1,6 @@
<item>
{{ partial "rss_item_metadata.rss" . }}
<description>{{ `<![CDATA[` | safeHTML }}
{{ .Content }}
]]></description>
</item>

28
layouts/index.atom.atom Normal file
View file

@ -0,0 +1,28 @@
{{- $pctx := . -}}
{{- if .IsHome -}} {{ $pctx = .Site }} {{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}} {{- $pages = $pages | first $limit -}} {{- end -}}
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ .Site.Title }}</title>
<link href="{{ `/feed.xml` | absURL }}" rel="self" />
<link href="{{ .Permalink }}" />
{{ if not .Date.IsZero }}<updated>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>{{ end }}
<id>{{ .Permalink }}</id>
{{ with .Site.Author.name }}
<author>
<name>{{ . }}</name>
{{ with $.Site.Author.email }}<email>{{ . }}</email>{{ end }}
<uri>{{ $.Site.Home.Permalink }}</uri>
</author>
{{ end }}
<generator version="{{ hugo.Version }}" uri="https://gohugo.io">Hugo {{ hugo.Version }}</generator>
<rights>© {{ now.Year }} Eryn Wells</rights>
{{ range $pages }}{{ .Render "atom_entry" }}{{ end }}
</feed>

16
layouts/index.html Normal file
View file

@ -0,0 +1,16 @@
{{ define "main" }}
<div class="platter grid">
<h1 class="site">{{ .Title }}</h1>
<div id="content">
{{ .Content }}
</div>
{{- with site.Menus.main -}}
{{- $url := $.RelPermalink -}}
<nav class="site bulleted">
{{- range . -}}
<li><a class="{{ if eq .URL $url }}active{{ end }}" href="{{ .URL }}"><span>{{ .Name }}</span></a></li>
{{- end -}}
</nav>
{{ end }}
</div>
{{ end }}

35
layouts/index.rss.rss Normal file
View file

@ -0,0 +1,35 @@
{{- $pageContext := . -}}
{{- if .IsHome -}}
{{ $pageContext = .Site }}
{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pageContext.RegularPages -}}
{{- else -}}
{{- $pages = $pageContext.Pages -}}
{{- end -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- 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>{{ .Site.Title }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content on {{ .Site.Title }}</description>
<generator>Hugo {{ hugo.Version }} -- gohugo.io</generator>
{{ with .Site.LanguageCode }}<language>{{ . }}</language>{{ end }}
{{ with .Site.Author }}
<managingEditor>{{ .name }} &lt;{{ .email }}&gt;</managingEditor>
<webMaster>{{ .name }} &lt;{{ .email }}&gt;</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 $pages }}{{ .Render "rss_item" }}{{ end }}
</channel>
</rss>

View file

@ -0,0 +1,10 @@
<title>{{ .Title }}</title>
<id>{{ .Permalink }}</id>
<published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
<updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
{{- range slice "series" "categories" "tags" -}}
{{- range $.GetTerms . -}}
{{- $scheme := (.Site.GetPage (printf "/%s" .Section)).Permalink -}}
<category term="{{ .Name }}" scheme="{{ $scheme }}" label="{{ .Title }}" />
{{- end -}}
{{- end -}}

View file

@ -0,0 +1,18 @@
<header>
{{ partial "development/draft_tag.html" . }}
<div class="post-title">
<h1>{{ .Title }}</h1>
{{ if not (eq .Type "page") }}
<div class="post-date"><time>{{ .Date | time.Format "January 2, 2006" }}</time></div>
{{ end }}
</div>
{{- if .Params.series -}}
{{- $series := .GetTerms "series" -}}
{{- if gt (len $series) 1 -}}
{{- errorf "More than one series for %s" .Permalink -}}
{{- end -}}
{{- with index $series 0 -}}
<span class="series"><a href="{{ .Permalink }}">{{ .Title }}</a></span>
{{- end -}}
{{- end -}}
</header>

View file

@ -1,3 +1,3 @@
{{ if in (.Site.BaseURL | string) "localhost" }}
{{ if .Draft }}<span class="draft">draft</span>{{ end }}
{{ end }}
{{ if .Draft }}<span class="draft">d</span>{{ end }}
{{ end }}

View file

@ -0,0 +1,15 @@
<details>
<summary>Debug EXIF Data</summary>
<table class="photo-params debug">
<thead>
<td>Key</td>
<td>Value</td>
</thead>
{{ range $k, $v := .Tags }}
<tr>
<td>{{ $k }}</td>
<td>{{ $v }}</td>
</tr>
{{ end }}
</table>
</details>

View file

@ -1,10 +1,8 @@
<footer class="site">
<div>
<ul class="slogans">
<li>Trans rights are human rights.</li>
<li>Black lives matter.</li>
<li>Get vaccinated.</li>
</ul>
<p>Copyright © <time datetime="{{ now.Year }}">{{ now.Year }}</time> <a href="{{ `` | absURL }}">Eryn Wells</a></p>
</div>
<ul class="slogans">
<li>Trans rights are human rights.</li>
<li>Black lives matter.</li>
<li>Get vaccinated.</li>
</ul>
<p>Copyright © <time datetime="{{ now.Year }}">{{ now.Year }}</time> <a href="{{ `` | absURL }}">Eryn Wells</a></p>
</footer>

View file

@ -0,0 +1,24 @@
{{- if or .Params.categories .Params.tags -}}
<ul class="tags">
{{- if .Params.categories -}}
{{- $categories := .GetTerms "categories" -}}
{{- if gt (len $categories) 1 -}}
{{- errorf "More than one category for %q" .Path -}}
{{- end -}}
{{- with index (.GetTerms "categories") 0 -}}
<li class="category"><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></li>
{{- end -}}
{{- end -}}
{{- if and .Params.categories .Params.tags -}}
<li class="chevron noselect"></li>
{{- end -}}
{{- if .Params.tags -}}
{{- range .GetTerms "tags" -}}
<li><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></li>
{{- end -}}
{{- end -}}
</ul>
{{- end -}}

View file

@ -2,19 +2,21 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000000">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
<title>{{ if not .IsHome }}{{ .Title }} - {{ end }}{{ site.Title }}</title>
{{- if eq .Kind "page" -}}
<meta name="description" content="{{ .Summary }}" />
<meta name="author" content="{{ .Params.Author | default site.Author.name }}">
{{- else -}}
<meta name="description" content="{{ .Params.description }}">
<meta name="author" content="{{ site.Author.name }}">
<meta name="description" content="{{ .Summary }}" />
<meta name="author" content="{{ .Params.Author | default site.Author.name }}">
{{ else }}
<meta name="description" content="{{ .Params.description }}">
<meta name="author" content="{{ site.Author.name }}">
{{- end -}}
{{- range $.Scratch.Get "social-list" -}}
<link rel="preload" as="image" href="{{ printf `%s.svg` . | absURL }}">
<link rel="preload" as="image" href="{{ printf `%s.svg` . | absURL }}">
{{- end -}}
<link rel="icon" href="{{ `favicon.ico` | absURL }}">
@ -23,7 +25,7 @@
{{ hugo.Generator }}
{{- range .AlternativeOutputFormats -}}
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink }}" title="{{ site.Title }}">
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink }}" title="{{ site.Title }}">
{{- end -}}
{{- if or hugo.IsProduction (eq site.Params.env "production") -}}

View file

@ -1,31 +1,24 @@
{{- $currentPage := . -}}
{{- $url := .RelPermalink -}}
<header class="site">
<div class="platter">
<div class="grid">
<h1 class="site">
<a class="site-name" href="{{ `` | absURL }}">Eryn Wells</a>
</h1>
{{ $url := .RelPermalink }}
{{ with site.Menus.main }}
<nav class="site bulleted">
<div class="platter grid">
<h1 class="site">
<a class="site-name" href="{{ `` | absURL }}">Eryn Wells</a>
</h1>
<nav class="site bulleted">
{{ with site.Menus.main }}
{{- range . -}}
<li><a {{ if $currentPage.HasMenuCurrent "main" . }} class="active"{{ end }} href="{{ .URL }}"><span>{{ .Name }}</span></a></li>
{{- end -}}
{{ end }}
</nav>
<nav class="social">
{{ with site.Menus.social }}
{{- range . -}}
{{- if eq .URL $url -}}
<li class="active"><span>{{ .Name }}</span></li>
{{- else -}}
<li><a href="{{ .URL }}"><span>{{ .Name }}</span></a></li>
{{- end -}}
{{- $targetBlank := .Params.targetBlank | default true -}}
<li><a style="--url: var(--{{ .Identifier }}-icon)" href="{{ .URL }}" {{ if $targetBlank }}target="_blank"{{ end }} aria-label="{{ .Name }}"><span>{{ .Params.shortName | default .Name }}</span></a></li>
{{- end -}}
</nav>
{{ end }}
<nav class="site social">
<li><a style="--url: var(--twitter-icon)" href="https://twitter.com/erynofwales" target="_blank" aria-label="twitter"><span>tw</span></a></li>
<li><a style="--url: var(--github-icon)" href="https://github.com/erynofwales" target="_blank" aria-label="github"><span>gh</span></a></li>
<li><a style="--url: var(--instagram-icon)" href="https://instagram.com/erynofwales" target="_blank" aria-label="instagram"><span>ig</span></a></li>
{{ with .OutputFormats.Get "rss" }}
<li><a style="--url: var(--rss-icon)" href="{{ .RelPermalink }}" aria-label="rss"><span>rss</span></a></li>
{{ end }}
</nav>
</div>
</nav>
</div>
</header>

View file

@ -0,0 +1,13 @@
{{ $orientation := .Exif.Tags.Orientation }}
{{/* EXIF orientation is specified as an integer corresponding to a 90º rotation. */}}
{{ $angle := 0 }}
{{ if in (slice 1 2) $orientation }}
{{ $orientation = 0 }}
{{ else if in (slice 3 4) $orientation }}
{{ $orientation = 180 }}
{{ else if in (slice 5 6) $orientation }}
{{ $orientation = 90 }}
{{ else if in (slice 7 8) $orientation }}
{{ $orientation = 270 }}
{{ end }}
{{ return $orientation }}

View file

@ -0,0 +1,23 @@
{{- $thumbnailResource := .Resources.GetMatch (index .Page.Params "thumbnail")
| default (index (.Page.Resources.ByType "image") 0) -}}
{{- if not $thumbnailResource -}}
{{- errorf "No thumbnail available for %s" .Page.Permalink }}
{{- end -}}
{{ $orientation := partial "images/orientation_angle.html" $thumbnailResource }}
{{ $targetWidth := 0 }}
{{ if isset . "Width" }}{{ $targetWidth = .Width }}{{ else }}{{ $targetWidth = $thumbnailResource.Width }}{{ end }}
{{ $targetHeight := 0 }}
{{ if isset . "Height" }}{{ $targetHeight = .Height }}{{ else }}{{ $targetHeight = $thumbnailResource.Height }}{{ end }}
{{ $thumbnail := false }}
{{ if not (and (eq $orientation 0)
(eq $targetWidth $thumbnailResource.Width)
(eq $targetHeight $thumbnailResource.Height)) }}
{{ $thumbnail = $thumbnailResource.Fit (printf "%dx%d r%d" $targetWidth $targetHeight (sub 360 $orientation)) }}
{{ else }}
{{ $thumbnail = $thumbnailResource }}
{{ end }}
{{ return $thumbnail }}

View file

@ -0,0 +1,31 @@
<div class="photo-params">
<div class="container">
<table>
<thead>
<td class="make-model" colspan=4>{{ .Tags.Make }} {{ .Tags.Model }}</td>
</thead>
<tr>
<td colspan="2" class="location">
{{ $lat := float .Lat }}{{ $latDir := cond (eq $lat 0) "" (cond (gt $lat 0) "N" "S") }}
<data class="latitude" value="{{ $lat }}">{{ .Lat | lang.FormatNumber (cond (ne $lat 0) 3 0) }}º{{ $latDir }}</data>,
{{ $long := float .Long }}{{ $longDir := cond (eq $long 0) "" (cond (gt $long 0) "E" "W") }}
<data class="longitude" value="{{ $long }}">{{ .Long | lang.FormatNumber (cond (ne $long 0) 3 0) }}º{{ $longDir }}</data>
</td>
<td colspan="2" class="size">
{{ $widthpx := .Tags.PixelXDimension }}
{{ $heightpx := .Tags.PixelYDimension }}
{{ if and (gt $widthpx 0) (gt $heightpx 0) }}
{{ $megapixels := div (mul $widthpx $heightpx) 1e6 }}
<data value="{{ $megapixels }}">{{ $megapixels | lang.FormatNumber 0 }} MP</data> • {{ $widthpx }} × {{ $heightpx }}
{{ end }}
</td>
</tr>
<tr class="exposure-attributes">
<td class="iso">{{ with .Tags.ISOSpeedRatings }}ISO {{ . }}{{ end }}</td>
<td class="focal-length">{{ with .Tags.FocalLengthIn35mmFilm }}{{ . }} mm{{ end }}</td>
<td class="f-number">{{ with .Tags.FNumber }}ƒ{{ . }}{{ end }}</td>
<td class="exposure-time">{{ with .Tags.ExposureTime }}{{ . }} s{{ end }}</td>
</tr>
</table>
</div>
</div>

View file

@ -0,0 +1,11 @@
<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>
{{- range slice "series" "categories" "tags" -}}
{{ range $.GetTerms . }}
{{- $domain := (.Site.GetPage (printf "/%s" .Section)).Permalink -}}
<category domain="{{ $domain }}">{{ .Title }}</category>
{{ end }}
{{- end -}}

View file

@ -1,51 +1,19 @@
<article class="post-single">
<header>
{{ partial "development/draft_tag.html" . }}
<div class="post-title">
<h1>{{ .Title }}</h1>
{{ if not (eq .Type "page") }}
<div class="post-date"><time>{{ .Date | time.Format "January 2, 2006" }}</time></div>
{{ end }}
</div>
</header>
{{ partial "content_header.html" . }}
<section class="post-content">
{{ .Content }}
</section>
{{ .Content }}
<footer>
{{- if or .Params.categories .Params.tags -}}
<ul class="tags">
{{- if .Params.categories -}}
{{- $categories := .GetTerms "categories" -}}
{{- if gt (len $categories) 1 -}}
{{- errorf "More than one category for %q" .Path -}}
{{- end -}}
{{- with index (.GetTerms "categories") 0 -}}
<li class="category"><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></li>
{{- end -}}
{{- end -}}
{{- if and .Params.categories .Params.tags -}}
<li class="chevron noselect"></li>
{{- end -}}
{{- if .Params.tags -}}
{{- range .GetTerms "tags" -}}
<li><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></li>
{{- end -}}
{{- end -}}
</ul>
{{- end -}}
<footer>
{{ partial "footer_tags.html" . }}
{{ $pages := where site.RegularPages "Type" "in" site.Params.mainSections }}
{{ if and (gt (len $pages) 1) (in $pages . ) }}
{{ $pages := where site.RegularPages "Type" "in" site.Params.mainSections }}
{{- if and (gt (len $pages) 1) (in $pages . ) -}}
<nav class="post-nav">
{{ with $pages.Prev . }}
<a class="prev" href="{{ .Permalink }}"><span></span><span>{{ .Name }}</span></a>
<a class="prev" href="{{ .Permalink }}"><span></span><span>{{ .Name }}</span></a>
{{ end }}
{{ with $pages.Next . }}
<a class="next" href="{{ .Permalink }}"><span>{{ .Name }}</span><span></span></a>
<a class="next" href="{{ .Permalink }}"><span>{{ .Name }}</span><span></span></a>
{{ end }}
</nav>
{{ end }}
</footer>
</article>
{{- end -}}
</footer>

View file

@ -0,0 +1,12 @@
{{- $thumbnail := partial "images/photo_thumbnail.html" . -}}
<entry>
{{ partial "atom_entry_metadata.xml" . }}
<link rel="enclosure" href="{{ $thumbnail.Permalink }}" type="{{ $thumbnail.MediaType }}" length="{{ len $thumbnail.Content }}" />
{{- $inlineThumbnail := partial "images/photo_thumbnail.html" (dict "Page" . "Width" 1280 "Height" 1280) -}}
<content type="html">{{ `<![CDATA[` | safeHTML }}
{{- with $inlineThumbnail -}}
<img src="{{ .Permalink }}">
{{- end -}}
{{- .Content -}}
]]></content>
</entry>

View file

@ -0,0 +1,6 @@
{{- $thumbnail := partial "images/photo_thumbnail.html" (dict "Page" . "Width" 600 "Height" 600) -}}
{{- $thumbnail = $thumbnail.Crop "600x600" -}}
{{- $altText := $thumbnail.Params.alt -}}
<a href="{{ .RelPermalink }}" title="{{ .Title }}">
<img src="{{ $thumbnail.RelPermalink }}" {{ with $altText }}alt="{{ . }}"{{ end }}>
</a>

20
layouts/photos/list.html Normal file
View file

@ -0,0 +1,20 @@
{{ define "header" }}
{{ partial "header.html" . }}
{{ end }}
{{ define "main" }}
{{- $pages := (.Paginate (first 50 (.Pages.GroupByDate "January 2006"))).PageGroups -}}
{{ $pages := .Pages.ByDate.GroupByDate "Jan 2006" }}
{{- range $pages -}}
<div>
<h6><span>{{ .Key | title }}</span></h6>
</div>
{{- range .Pages -}}
{{- .Render "li_thumbnail_in_grid" -}}
{{- end -}}
{{- end -}}
{{ end }}
{{ define "footer" }}
{{ partial "footer.html" . }}
{{ end }}

View file

@ -0,0 +1,8 @@
{{- $thumbnail := partial "images/photo_thumbnail.html" (dict "Page" . "Width" 1280 "Height" 1280) -}}
<item>
{{ partial "rss_item_metadata.rss" . }}
<description>{{ `<![CDATA[` | safeHTML }}
<img src="{{ $thumbnail.Permalink }}">
{{ .Content }}
]]></description>
</item>

View file

@ -0,0 +1,43 @@
{{ define "header" }}
{{ partial "header.html" . }}
{{ end }}
{{ define "main" }}
{{- if .Title -}}
{{ partial "content_header.html" . }}
{{- end -}}
{{ $photos := .Resources.ByType "image" }}
{{ if eq (len $photos) 0 }}
{{ errorf "Missing photo from photos page %q" .Path }}
{{ end }}
{{ if eq (len $photos) 1 }}
{{- $img := index $photos 0 -}}
<figure><img src="{{ $img.RelPermalink }}"></figure>
{{ .Content }}
{{- partial "photo_exif_table.html" $img.Exif -}}
{{- if in ($.Site.BaseURL | string) "localhost" -}}
{{- partial "development/photo_exif_table.html" $img.Exif -}}
{{- end -}}
{{ else }}
<figure>
<ul class="carousel">
{{- range $photos -}}
<li>{{ . }}</li>
{{- end -}}
</ul>
</figure>
{{ end }}
<footer>
{{ partial "footer_tags.html" . }}
</footer>
{{ end }}
{{ define "footer" }}
{{ partial "footer.html" . }}
{{ end }}

View file

@ -1,5 +1,4 @@
{{ $id := .Get "id" }}
{{ .Page.Scratch.Set "includes_p5_sketch" true }}
{{- $id := .Get "id" -}}
<div class="centered">
<figure class="p5-sketch {{ with .Get "bordered" }}bordered{{ end }}" id="{{ $id }}"></figure>
</div>

View file

@ -1,11 +1,3 @@
{{ $id := .Get "id" }}
{{ .Page.Scratch.Set "includes_railroad_diagram" true }}
<div class="centered">
<figure class="railroad-diagram" {{ if $id }}id="{{ $id }}"{{ end }}></figure>
</div>
<script defer type="module">
import { railroadDiagram } from {{ `/scripts/railroad-utils.js` | relURL }};
railroadDiagram(rr => {
{{ .Inner | safeJS }}
}, "{{ $id }}");
</script>
{{- $id := .Get "id" -}}
<figure class="railroad-diagram" {{ if $id }}id="{{ $id }}"{{ end }}></figure>
{{ .Inner }}

View file

@ -0,0 +1,9 @@
{{- with $photoPage := $.Page.GetPage (printf "photos/%s" (.Get 0)) -}}
{{- $thumbnail := partial "images/photo_thumbnail.html" (dict "Page" . "Width" 1280 "Height" 1280) -}}
{{- $altText := $thumbnail.Params.alt | default .Title -}}
<a href="{{ .RelPermalink }}" title="{{ .Title }}">
<img src="{{ $thumbnail.RelPermalink }}"{{ with $altText }} alt="{{ . }}"{{ end }}>
</a>
{{- else -}}
{{- errorf "No page matching '%s'" (.Get 0) -}}
{{- end -}}

View file

@ -0,0 +1,8 @@
{{- $parentID := .Parent.Get "id" -}}
{{- $narrowOnly := .Get "narrow" }}
<script defer type="module">
import { railroadDiagram } from {{ `/scripts/railroad-utils.js` | relURL }};
railroadDiagram(rr => {
{{ .Inner | safeJS }}
}, "{{ $parentID }}", {{ if $narrowOnly }}true{{ else }}false{{ end }});
</script>

57
logo.html Normal file
View file

@ -0,0 +1,57 @@
<!doctype html>
<html>
<body>
<h1>Site Logo</h1>
<div id="logo">E</div>
</body>
<style>
@font-face {
font-family: "Museo_Slab";
src: url("static/fonts/Museo_Slab_500.woff2") format("woff2"),
url("static/fonts/Museo_Slab_500.woff") format("woff");
font-weight: normal;
font-style: normal;
}
:root {
--lt-blue: rgb(69, 212, 243);
--mid-blue: rgb(26, 169, 239);
--dk-blue: rgb(63, 46, 231);
--purple: rgb(161, 49, 232);
--lilac: rgb(187, 121, 245);
}
* { box-sizing: border-box; }
body {
color: #222;
padding-block-start: 6vmin;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
font-family: "Museo_Slab";
}
#logo {
color: transparent;
border: 0.5rem solid #222;
border-radius: 6px;
padding-top: 30px;
display: flex;
justify-content: center;
align-items: center;
width: 500px;
height: 500px;
background-color: blue;
background:
radial-gradient(circle at 20% 70%, var(--purple), transparent 40%),
radial-gradient(circle at 30% 30%, var(--lt-blue), var(--mid-blue) 20%, transparent 80%),
radial-gradient(ellipse at 95% 20%, var(--dk-blue), var(--mid-blue) 70%, transparent 80%),
radial-gradient(circle at 100% 100%, var(--purple), var(--lilac) 100%),
radial-gradient(circle at 45% 100%, var(--lilac), var(--purple) 60%),
radial-gradient(ellipse at 50% 50%, var(--dk-blue), transparent 80%);
background-clip: text;
font-size: 600px;
text-align: center;
line-height: 0.8;
}
</style>
</html>

View file

@ -1,6 +1,63 @@
import rr from "./railroad.js";
export function railroadDiagram(builder, elementID) {
const diagram = builder(rr);
diagram.addTo(document.getElementById(elementID));
class RailroadDiagramManager {
constructor() {
this.figures = new Map();
this.isCurrentlyNarrow = undefined;
this.diagramBreakpoint = window.matchMedia("(max-width: 450px)");
this.diagramBreakpoint.addEventListener("change", () => {
this.updateVisiblity();
});
}
add(svg) {
const parent = svg.parentElement;
if (!this.figures.has(parent)) {
this.figures.set(parent, []);
}
this.figures.get(parent).push(svg);
parent.removeChild(svg);
}
updateVisiblity() {
const isNarrow = this.diagramBreakpoint.matches;
if (isNarrow === this.isCurrentlyNarrow) {
return;
}
this.isCurrentlyNarrow = isNarrow;
for (let [figure, svgs] of this.figures.entries()) {
for (let svg of svgs) {
const svgHasNarrowClass = svg.classList.contains("narrow");
if (isNarrow && svgHasNarrowClass)
figure.appendChild(svg);
else if (!isNarrow && !svgHasNarrowClass)
figure.appendChild(svg);
else if (svg.parentElement === figure)
figure.removeChild(svg);
}
}
}
}
let railroadDiagramManager = new RailroadDiagramManager();
export function railroadDiagram(builder, elementID, isNarrow) {
const diagram = builder(rr);
const svg = diagram.addTo(document.getElementById(elementID));
if (isNarrow) {
svg.classList.add("narrow");
}
railroadDiagramManager.add(svg);
}
window.addEventListener("DOMContentLoaded", () => {
railroadDiagramManager.updateVisiblity();
});

View file

@ -3,6 +3,10 @@
* Eryn Wells <eryn@erynwells.me>
*/
details:has(.photo-params.debug) {
margin-block-end: var(--body-item-spacing);
}
.draft {
color: red;
display: inline-block;
@ -34,25 +38,30 @@
width: max-content;
}
#debug-page-info details {
#debug-page-info > details {
margin: 0.5rem;
}
#debug-page-info summary {
#debug-page-info > summary {
font-family: var(--font-family-heading);
}
#debug-page-info table {
#debug-page-info > details > table {
border-collapse: collapse;
display: block;
margin-block-start: 0.5em;
margin-inline-start: 1em;
}
#debug-page-info td {
#debug-page-info > details > table > tbody > tr > td {
border: 1px solid rgb(var(--dk-gray));
padding: 0.3em;
}
#debug-page-info tr:nth-child(even) {
#debug-page-info > details > table > tbody > tr:nth-child(even) {
background-color: var(--separator-color);
}
.photo-params.debug {
width: 100%;
}

View file

@ -9,7 +9,7 @@
svg.railroad-diagram path {
stroke-width: 2;
stroke: var(--foreground-body-color);
stroke: var(--html-color);
fill: none;
}
@ -17,7 +17,7 @@ svg.railroad-diagram text {
font-family: var(--font-family-monospace);
text-anchor: middle;
white-space: pre;
fill: var(--foreground-body-color);
fill: var(--html-color);
}
svg.railroad-diagram text.diagram-text {
@ -42,7 +42,7 @@ svg.railroad-diagram g.non-terminal text {
svg.railroad-diagram rect {
stroke-width: 2;
stroke: var(--foreground-body-color);
stroke: var(--html-color);
fill: var(--rect-fill);
}
@ -54,8 +54,8 @@ svg.railroad-diagram rect.group-box {
svg.railroad-diagram path.diagram-text {
stroke-width: 2;
stroke: var(--foreground-body-color);
fill: var(--background-body-color);
stroke: var(--html-color);
fill: var(--html-background-color);
cursor: help;
}

View file

@ -17,9 +17,6 @@
--purple: 161, 49, 232;
--lilac: 187, 121, 245;
--background-color: rgb(var(--white));
--foreground-body-color: rgba(var(--black), 0.8);
--foreground-header-color: rgb(var(--black));
--site-nav-link-color: rgb(var(--mid-blue));
--separator-color: rgb(var(--lt-gray));
@ -38,12 +35,27 @@
--body-font-size: 2rem;
--body-item-spacing: 1em;
--body-line-height: 1.4;
--body-line-height: 1.5;
--body-header-line-height: 1.1;
--body-code-background-color: rgb(var(--super-lt-gray));
--heading-color: rgb(var(--black));
--header-series-arrow-foreground-color: rgb(var(--sub-dk-gray));
--html-background-color: rgb(var(--white));
--html-color: rgba(var(--black), 0.8);
--platter-background-color: rgba(var(--white), var(--platter-background-opacity));
--platter-background-opacity: 0.6;
--platter-backdrop-filter: blur(10px);
--content-width: 80rem;
--tag-foreground-color: rgb(var(--super-dk-gray));
--tag-background-color: rgb(var(--super-lt-gray));
--tag-spacer-foreground-color: rgb(var(--super-dk-gray));
--tag-hover-background-color: rgb(var(--sub-lt-gray));
--transition-duration: 0.7s;
--social-menu-padding: 1rem;
@ -51,56 +63,40 @@
--twitter-icon: url(/icons/twitter.svg);
--github-icon: url(/icons/github.svg);
--instagram-icon: url(/icons/instagram.svg);
--rss-icon: url(/icons/rss.svg);
--feed-icon: url(/icons/rss.svg);
}
@media (prefers-color-scheme: dark) {
:root {
--background-color: rgb(var(--black));
--foreground-body-color: rgba(var(--white), 0.8);
--foreground-header-color: rgb(var(--white));
--separator-color: rgb(var(--dk-gray));
--box-shadow-color: rgba(var(--dk-gray), 0.8);
--body-code-background-color: rgb(var(--dk-gray));
--heading-color: rgb(var(--white));
--header-series-arrow-foreground-color: rgb(var(--super-dk-gray));
--html-background-color: rgb(var(--black));
--html-color: rgba(var(--white), 0.8);
--platter-background-color: rgba(var(--black), var(--platter-background-opacity));
--platter-backdrop-filter: brightness(0.66) blur(10px);
--tag-foreground-color: rgb(var(--sub-lt-gray));
--tag-background-color: rgb(var(--dk-gray));
--tag-spacer-foreground-color: rgb(var(--super-dk-gray));
--tag-hover-background-color: rgb(var(--super-dk-gray));
--tag-hover-foreground-color: rgb(var(--mid-gray));
--twitter-icon: url(/icons/twitter-dark.svg);
--github-icon: url(/icons/github-dark.svg);
--instagram-icon: url(/icons/instagram-dark.svg);
--rss-icon: url(/icons/rss-dark.svg);
--feed-icon: url(/icons/rss-dark.svg);
}
}
@layer reset {
* { box-sizing: border-box; }
body, button, h1, h2, h3, h4, h5, h6, input, ol, ul, p, pre, textarea {
padding: 0; margin: 0;
}
table {
border-spacing: 0;
border-collapse: collapse;
}
button,
input,
textarea {
font: inherit;
background: transparent;
border: 0;
outline: 0;
-webkit-appearance: none;
}
button,
input[type='button'],
input[type='submit'] {
cursor: pointer;
}
input:-webkit-autofill,
textarea:-webkit-autofill {
box-shadow: 0 0 0 6rem var(--white) inset;
}
}
a {
@ -120,7 +116,6 @@ blockquote {
}
body {
color: var(--foreground-body-color);
font-size: var(--body-font-size);
line-height: var(--body-line-height);
}
@ -135,6 +130,7 @@ code {
figcaption {
font-size: 75%;
line-height: var(--body-line-height);
margin-block-start: 0.2em;
text-align: center;
}
@ -142,6 +138,7 @@ figcaption {
figure {
border-radius: 6px;
display: inline-block;
line-height: 1;
margin: 0;
margin-block: 0 var(--body-item-spacing);
margin-inline: 0;
@ -185,18 +182,11 @@ footer.site {
display: flex;
flex-direction: column;
font-size: 1.6rem;
margin-block-start: 2rem;
margin-block: var(--body-item-spacing);
text-align: center;
}
footer.site > div {
border-top: 1px solid var(--footer-border-color);
padding-block-start: 2rem;
max-width: var(--content-width);
width: 100%;
}
footer.site ul {
footer.site > ul {
align-items: center;
display: flex;
flex-wrap: wrap;
@ -204,11 +194,11 @@ footer.site ul {
list-style: none;
}
footer.site .slogans li {
footer.site > .slogans > li {
margin-inline-start: 0.5em;
}
footer.site p {
footer.site > p {
margin: 0;
}
@ -222,8 +212,16 @@ footer.site p + p {
}
}
h1 { font-size: 1.6em; }
h2 { font-size: 1.5em; }
h3 { font-size: 1.4em; }
h4 { font-size: 1.3em; }
h5 { font-size: 1.3em; }
h6 { font-size: var(--body-font-size); }
h5, h6 { font-family: var(--font-family-body); }
h1, h2, h3, h4, h5, h6 {
color: var(--foreground-header-color);
color: var(--heading-color);
font-family: var(--font-family-heading);
font-weight: 600;
margin-block: 3.5rem 1rem;
@ -231,11 +229,6 @@ h1, h2, h3, h4, h5, h6 {
line-height: var(--body-header-line-height);
}
h1:first-child, h2:first-child, h3:first-child,
h4:first-child, h5:first-child, h6:first-child {
margin-block-start: 0;
}
h1.site {
color: rgb(var(--mid-blue));
font-size: 2.5em;
@ -255,8 +248,8 @@ h1.site {
}
}
h1.site * { color: inherit; }
h1.site a:hover { text-decoration: none; }
h1.site > a { color: inherit; }
h1.site > a:hover { text-decoration: none; }
header.site {
display: flex;
@ -281,52 +274,56 @@ header.site.animated {
}
}
header.site h1 {
header.site > .grid > h1 {
font-family: var(--font-family-site-heading);
font-size: 2em;
margin: 0;
order: 1;
white-space: nowrap;
}
header.site .grid {
header.site > .grid {
align-items: baseline;
display: grid;
display: flex;
flex-wrap: wrap;
gap: 0 0.5em;
grid-template-columns: max-content auto max-content;
grid-template-areas:
"title menu social";
margin: 0 auto;
max-width: var(--content-width);
width: var(--content-width);
padding: 1.5rem 3rem;
width: 100%;
}
@media (max-width: 450px) {
header.site .grid {
grid-template-columns: repeat(2, max-content);
grid-template-rows: repeat(2, max-content);
grid-template-areas:
"title social"
"menu menu";
max-width: var(--content-width);
width: inherit;
header.site > .grid > nav:first-of-type {
justify-content: start;
order: 2;
}
header.site > .grid > nav:last-of-type {
justify-content: end;
order: 3;
}
@media (max-width: 516px) {
header.site > .platter {
border-left: none;
border-radius: 0;
border-right: none;
border-top: none;
}
header.site > .grid {
max-width: none;
}
}
header.site .platter {
margin: 0 auto;
padding: 1.5rem 3rem;
}
header.site .grid nav:first-of-type {
grid-area: menu;
justify-content: start;
}
header.site .grid nav:last-of-type {
grid-area: social;
justify-content: end;
@media (max-width: 435px) {
header.site > .grid > nav:first-of-type {
order: 5;
}
}
html {
background-color: var(--background-color);
color: var(--foreground-color);
background-color: var(--html-background-color);
color: var(--html-color);
font-family: var(--font-family-body);
font-size: clamp(var(--font-size-min), 1vw, var(--font-size-max));
}
@ -339,24 +336,25 @@ img {
img.circular {
shape-outside: circle(50%);
-webkit-clip-path: circle(50%);
clip-path: circle(50%);
}
main {
box-sizing: border-box;
max-width: var(--content-width);
margin: var(--body-item-spacing) auto;
padding-inline: var(--body-item-spacing);
width: 100%;
}
main h1 { font-size: 1.6em; }
main h2 { font-size: 1.5em; }
main h3 { font-size: 1.4em; }
main h4 { font-size: 1.3em; }
main h5 { font-size: 1.3em; }
main h6 { font-size: var(--body-font-size); }
main > header {
margin-bottom: var(--body-item-spacing);
}
main h5, main h6 { font-family: var(--font-family-body); }
main > :first-child { margin-block-start: 0; }
main > :last-child { margin-block-end: 0; }
nav.site {
header.site > .grid > nav {
align-items: center;
display: flex;
font-size: max(1.5rem, 80%);
@ -366,21 +364,22 @@ nav.site {
text-transform: lowercase;
}
nav.site li {
header.site > .grid > nav > li {
color: var(--site-nav-link-color);
display: block;
margin-inline-end: 0.5em;
}
nav.site .active { font-weight: bold; }
nav.bulleted li:first-child::before {
color: var(--foreground-color);
header.site > .grid > nav > .active { font-weight: bold; }
nav.bulleted > li:first-child::before {
color: var(--html-color);
content: "";
margin-inline-end: 0;
}
nav.bulleted li::before {
color: var(--foreground-header-color);
nav.bulleted > li::before {
color: var(--heading-color);
content: "•";
font-size: 60%;
font-weight: normal;
@ -391,9 +390,8 @@ nav.bulleted li::before {
p {
letter-spacing: 0.025em;
line-height: var(--body-line-height);
margin-block-end: var(--body-item-spacing);
}
p { margin-block-end: var(--body-item-spacing); }
p:last-child { margin-block-end: 0; }
ul ul,
ul ol,
@ -402,6 +400,18 @@ ol ol {
margin-inline-start: var(--body-item-spacing);
}
header > span.series {
font-size: 1.75rem;
letter-spacing: 1px;
margin-inline-start: 0.5em;
}
header > span.series::before {
color: var(--header-series-arrow-foreground-color);
content: "↳";
margin-inline-end: 0.25em;
}
.post-list {
list-style: none;
margin: 0;
@ -424,62 +434,13 @@ ol ol {
display: grid;
gap: 1em;
padding: 0.1em;
transition: background-color 0.25s;
}
@supports not (display: subgrid) {
.post-list li {
grid-template-columns: 100px 20px 3px auto;
grid-template-areas: "month day thing title";
}
}
@supports (display: subgrid) {
.post-list li {
display: subgrid;
}
}
.post-list li time:nth-of-type(1) {
grid-area: month;
text-align: end;
}
.post-list li time:nth-of-type(2) {
grid-area: day;
text-align: end;
}
.post-list li .title {
font-family: var(--font-family-body);
grid-area: title;
text-align: start;
}
.post-list li:hover {
background-color: var(--post-item-highlight-color);
}
.post-list h1 {
font-size: inherit;
font-weight: normal;
margin: 0;
padding: 0;
}
.post-list a {
color: inherit;
}
.post-list a:hover {
text-decoration: none;
}
.post-title {
align-items: baseline;
display: flex;
flex-wrap: wrap;
gap: 0 4rem;
margin-bottom: var(--body-item-spacing);
}
.post-title h1 {
@ -505,17 +466,14 @@ ol ol {
width: 100%;
}
.social menu li + li {
margin-left: var(--social-menu-padding);
}
.social {
display: flex;
display: block;
letter-spacing: 2px;
margin-left: auto;
order: 2;
}
.social a {
.social > li > a {
background-image: var(--url);
background-repeat: no-repeat;
background-size: var(--menu-icon-size);
@ -524,7 +482,7 @@ ol ol {
width: var(--menu-icon-size);
}
.social a span {
.social > li > a > span {
display: none;
}
@ -533,6 +491,58 @@ ol ol {
padding: 0 0.5rem;
}
footer > .tags {
display: flex;
padding-inline-start: 0;
}
footer > .tags > li {
background-color: var(--tag-background-color);
color: var(--tag-foreground-color);
border-radius: 4px;
display: inline-block;
font-size: 75%;
letter-spacing: 1px;
}
footer > .tags > li:hover {
background-color: var(--tag-hover-background-color);
}
footer > .tags > li > a {
color: inherit;
display: block;
height: 100%;
width: 100%;
padding: 0.6rem 1rem;
}
footer > .tags > li > a:hover {
color: var(--tag-hover-foreground-color);
text-decoration: none;
}
footer > .tags > li + li { margin-inline-start: 1rem; }
footer > .tags > li.chevron + li { margin-inline-start: 0 }
footer > .tags > .chevron {
align-items: center;
border: 0;
border-radius: 0;
color: var(--tag-spacer-foreground-color);
background-color: inherit;
display: flex;
justify-content: center;
margin-inline-start: 0;
padding-inline-start: 1px;
width: 2rem;
}
footer > .tags > .chevron:hover {
color: var(--tag-spacer-foreground-color);
background-color: inherit;
}
.youtube iframe {
aspect-ratio: 16 / 9;
margin-bottom: -3px;
@ -577,7 +587,9 @@ ol ol {
}
.platter {
background: var(--background-color);
-webkit-backdrop-filter: var(--platter-backdrop-filter);
backdrop-filter: var(--platter-backdrop-filter);
background-color: var(--platter-background-color);
border: 1px solid var(--header-border-color);
border-radius: 12px;
box-shadow: 3px 4px 4px var(--header-box-shadow-color);