Compare commits
11 Commits
d2770b9a5a
...
astro
| Author | SHA1 | Date | |
|---|---|---|---|
| 67779ecd40 | |||
| eda7d3aa00 | |||
| e0af57e5b1 | |||
| 675ef1003d | |||
| 68bb25357b | |||
| 3f24f42c1f | |||
| bfdaac8095 | |||
| e02af4c552 | |||
| fd8ed38572 | |||
| 5ffb51d5ce | |||
| 485e3a0a91 |
@@ -1,6 +1,7 @@
|
|||||||
import { defineConfig } from "astro/config";
|
import { defineConfig } from "astro/config";
|
||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx';
|
||||||
import vue from '@astrojs/vue';
|
import vue from '@astrojs/vue';
|
||||||
|
import { remarkDescription } from './src/plugins.ts';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [
|
integrations: [
|
||||||
@@ -9,6 +10,7 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
prefetch: true,
|
prefetch: true,
|
||||||
markdown: {
|
markdown: {
|
||||||
|
remarkPlugins: [remarkDescription],
|
||||||
shikiConfig: {
|
shikiConfig: {
|
||||||
themes: {
|
themes: {
|
||||||
light: 'dracula',
|
light: 'dracula',
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ My word-search grid appears to be 140 characters by 140, so I'm just going to ha
|
|||||||
|
|
||||||
Not gonna lie here, this part took me _way_ longer than I expected it to. See, the standard way to read a file in Fortran is with the `read()` statement. (It looks like a function call, but it's not.) You use it something like this:
|
Not gonna lie here, this part took me _way_ longer than I expected it to. See, the standard way to read a file in Fortran is with the `read()` statement. (It looks like a function call, but it's not.) You use it something like this:
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
read(file_handle, *) somevar, anothervar, anothervar2
|
read(file_handle, *) somevar, anothervar, anothervar2
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ Or at least, that's one way of using it. But here's the problem: by default, For
|
|||||||
|
|
||||||
Initially, I thought I might be able to do this:
|
Initially, I thought I might be able to do this:
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
character, dimension(140, 140) :: grid
|
character, dimension(140, 140) :: grid
|
||||||
|
|
||||||
! ...later
|
! ...later
|
||||||
@@ -70,7 +70,7 @@ But sadly, this kept spitting out errors about how it had encountered the end of
|
|||||||
|
|
||||||
My next try looked something like this:
|
My next try looked something like this:
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
do row = 1, 100
|
do row = 1, 100
|
||||||
read(file_handle, *) grid(row, :)
|
read(file_handle, *) grid(row, :)
|
||||||
end do
|
end do
|
||||||
@@ -82,7 +82,7 @@ I'm pretty sure the proper way to do this would be to figure out how to set the
|
|||||||
|
|
||||||
So instead, I decided to just make it dumber.
|
So instead, I decided to just make it dumber.
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
program advent04
|
program advent04
|
||||||
implicit none
|
implicit none
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ S A M X M A S
|
|||||||
S . . S . . S
|
S . . S . . S
|
||||||
```
|
```
|
||||||
|
|
||||||
Which has all 8 possible orientationS of the word `XMAS` starting from the central X. Then, we can just take a sliding "window" of the same size into our puzzle grid and compare it to the test grid. This is a native operation in Fortran--comparing two arrays of the same size results in a third array whose elements are the result of each individual comparison from the original arrays. Then we can just call `count()` on the resulting array to get the number of true values, and we know how many characters matched up. Subtract 1 for the central X we already knew about, then divide by 3 since there are 3 letters remaining in each occurrence of `XMAS`, and Bob's your uncle, right?
|
Which has all 8 possible orientations of the word `XMAS` starting from the central X. Then, we can just take a sliding "window" of the same size into our puzzle grid and compare it to the test grid. This is a native operation in Fortran--comparing two arrays of the same size results in a third array whose elements are the result of each individual comparison from the original arrays. Then we can just call `count()` on the resulting array to get the number of true values, and we know how many characters matched up. Subtract 1 for the central X we already knew about, then divide by 3 since there are 3 letters remaining in each occurrence of `XMAS`, and Bob's your uncle, right?
|
||||||
|
|
||||||
...Wait, no. That won't work because it doesn't account for partial matches. Say we had a "window" that looked like this (I'm only showing the bottom-right quadrant of the window for simplicity):
|
...Wait, no. That won't work because it doesn't account for partial matches. Say we had a "window" that looked like this (I'm only showing the bottom-right quadrant of the window for simplicity):
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ Will this work? Is it even marginally more efficient than the stupidly obvious w
|
|||||||
|
|
||||||
Ok, first things first. Let's adjust the data-loading code to pad the grid with 3 bogus values on each edge, so that we can still generate our window correctly when we're looking at a point near the edge of the grid.
|
Ok, first things first. Let's adjust the data-loading code to pad the grid with 3 bogus values on each edge, so that we can still generate our window correctly when we're looking at a point near the edge of the grid.
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
grid = '.' ! probably wouldn't matter if we skipped this, uninitialized memory just makes me nervous
|
grid = '.' ! probably wouldn't matter if we skipped this, uninitialized memory just makes me nervous
|
||||||
|
|
||||||
open(newunit=handle, file="data/04.txt", status="old", action="read")
|
open(newunit=handle, file="data/04.txt", status="old", action="read")
|
||||||
@@ -196,7 +196,7 @@ Oh. Oh, _noooo_.
|
|||||||
|
|
||||||
It's okay, I mean, uh, it's not _that_ much higher. Only two orders of magnitude, and what are the odds of all eight versions of `XMAS` appearing in the same window, anyway? Something like 1/4<sup>25</sup>? Maybe we can still make this work.
|
It's okay, I mean, uh, it's not _that_ much higher. Only two orders of magnitude, and what are the odds of all eight versions of `XMAS` appearing in the same window, anyway? Something like 1/4<sup>25</sup>? Maybe we can still make this work.
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
integer function count_xmas(row, col) result(count)
|
integer function count_xmas(row, col) result(count)
|
||||||
implicit none
|
implicit none
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ Those `&`s are line-continuation characters, by the way. Apparently you can't ha
|
|||||||
|
|
||||||
Now we just have to put it all together:
|
Now we just have to put it all together:
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
total = 0
|
total = 0
|
||||||
do col = 4, 143
|
do col = 4, 143
|
||||||
do row = 4, 143
|
do row = 4, 143
|
||||||
@@ -314,7 +314,7 @@ Hmm, I wonder if there's a way to take a single starting test grid and manipulat
|
|||||||
|
|
||||||
Turns out, yes! Yes there is. We can use a combination of slicing with a negative step, and transposing, which switches rows with columns, effectively rotating and flipping the array. So setting up our test grids looks like this:
|
Turns out, yes! Yes there is. We can use a combination of slicing with a negative step, and transposing, which switches rows with columns, effectively rotating and flipping the array. So setting up our test grids looks like this:
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
character, dimension(3, 3) :: window, t1, t2, t3, t4
|
character, dimension(3, 3) :: window, t1, t2, t3, t4
|
||||||
|
|
||||||
t1 = reshape( &
|
t1 = reshape( &
|
||||||
@@ -332,7 +332,7 @@ t4 = t3(:, 3:1:-1) ! flip t3 left-to-right
|
|||||||
|
|
||||||
Then we can just compare the window to each test grid:
|
Then we can just compare the window to each test grid:
|
||||||
|
|
||||||
```fortran
|
```f90
|
||||||
window = grid(row - 1:row + 1, col - 1:col + 1)
|
window = grid(row - 1:row + 1, col - 1:col + 1)
|
||||||
if ( &
|
if ( &
|
||||||
count_matches(window, t1) == 5 &
|
count_matches(window, t1) == 5 &
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Example after post
|
title: Example after post
|
||||||
date: 2026-02-28
|
date: 2026-02-28
|
||||||
|
draft: true
|
||||||
---
|
---
|
||||||
|
|
||||||
## After
|
## After
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
---
|
---
|
||||||
export interface Props {
|
export interface Props {
|
||||||
name: string,
|
name: string,
|
||||||
|
width?: string,
|
||||||
|
height?: string,
|
||||||
|
display?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { name } = Astro.props;
|
const { name, width, height, display } = Astro.props;
|
||||||
|
|
||||||
const icons = import.meta.glob<{string: string}>('@components/icons/*.svg', { query: '?raw', import: 'default' });
|
const icons = import.meta.glob<{string: string}>('@components/icons/*.svg', { query: '?raw', import: 'default' });
|
||||||
const path = `/src/components/icons/${name}.svg`;
|
const path = `/src/components/icons/${name}.svg`;
|
||||||
@@ -13,11 +16,12 @@ if (icons[path] === undefined) {
|
|||||||
const icon = await icons[path]();
|
const icon = await icons[path]();
|
||||||
---
|
---
|
||||||
|
|
||||||
<Fragment set:html={icon} />
|
<span class="icon" set:html={icon} />
|
||||||
|
|
||||||
<style>
|
<style define:vars={{ width, height, display }}>
|
||||||
svg {
|
.icon :global(svg) {
|
||||||
width: 100%;
|
display: var(--display, block);
|
||||||
height: 100%;
|
width: var(--width, 100%);
|
||||||
|
height: var(--height, 100%);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,16 +8,54 @@ import { render } from 'astro:content';
|
|||||||
import Toc from '@components/Toc.vue';
|
import Toc from '@components/Toc.vue';
|
||||||
import { formatDate } from '@lib/datefmt';
|
import { formatDate } from '@lib/datefmt';
|
||||||
|
|
||||||
|
import Icon from '@components/Icon.astro';
|
||||||
|
import { headingElements } from '@components/headings';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
entry: CollectionEntry<'posts'>,
|
entry: CollectionEntry<'posts'>,
|
||||||
prevSlug: string | null,
|
prevSlug?: string | null,
|
||||||
nextSlug: string | null,
|
nextSlug?: string | null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { entry, prevSlug, nextSlug } = Astro.props;
|
const { entry, prevSlug, nextSlug } = Astro.props;
|
||||||
const { Content, headings } = await render(entry);
|
const { Content, headings } = await render(entry);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<article class="prose" data-dropcap-style={entry.data.dropcap}>
|
||||||
|
<header class="title">
|
||||||
|
<h1>
|
||||||
|
<!-- <SmallCaps text={entry.data.title} upperWeight={500} lowerWeight={800} /> -->
|
||||||
|
{ entry.data.title }
|
||||||
|
</h1>
|
||||||
|
<p class="subtitle">{ formatDate(entry.data.date) }</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="left-gutter">
|
||||||
|
<Toc client:load {headings} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="post">
|
||||||
|
<Content components={headingElements} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div id="right-gutter" />
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
{prevSlug && (
|
||||||
|
<div class="footer-link left">
|
||||||
|
<Icon name="arrow-left" height="1em" />
|
||||||
|
<a href={`/${prevSlug}`} data-astro-prefetch>Older</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{nextSlug && (
|
||||||
|
<div class="footer-link right">
|
||||||
|
<a href={`/${nextSlug}`} data-astro-prefetch>Newer</a>
|
||||||
|
<Icon name="arrow-right" height="1em" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* 3-column grid: left gutter, center content, and right gutter */
|
/* 3-column grid: left gutter, center content, and right gutter */
|
||||||
article {
|
article {
|
||||||
@@ -59,7 +97,11 @@ footer {
|
|||||||
grid-column: 2 / 3;
|
grid-column: 2 / 3;
|
||||||
margin-bottom: 2.5rem;
|
margin-bottom: 2.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
|
color: var(--content-color-faded);
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 2.25rem;
|
||||||
|
border-top: 1px solid var(--content-color-faded);
|
||||||
|
|
||||||
& a {
|
& a {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
@@ -77,6 +119,32 @@ footer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* basic styles */
|
||||||
|
.footer-link {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* animate on hover */
|
||||||
|
.footer-link {
|
||||||
|
& :global(svg) {
|
||||||
|
transition: transform 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right:hover :global(svg) {
|
||||||
|
transform: translateX(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left:hover :global(svg) {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
article {
|
article {
|
||||||
& :global(section.post::first-letter) {
|
& :global(section.post::first-letter) {
|
||||||
font-family: 'Baskervville';
|
font-family: 'Baskervville';
|
||||||
@@ -94,32 +162,3 @@ article {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<article class="prose" data-dropcap-style={entry.data.dropcap}>
|
|
||||||
<header class="title">
|
|
||||||
<h1>
|
|
||||||
<!-- <SmallCaps text={entry.data.title} upperWeight={500} lowerWeight={800} /> -->
|
|
||||||
{ entry.data.title }
|
|
||||||
</h1>
|
|
||||||
<p class="subtitle">{ formatDate(entry.data.date) }</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div id="left-gutter">
|
|
||||||
<Toc client:load {headings} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="post">
|
|
||||||
<Content />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div id="right-gutter" />
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
{prevSlug && (
|
|
||||||
<a href={`/${prevSlug}`} data-astro-prefetch>Older</a>
|
|
||||||
)}
|
|
||||||
{nextSlug && (
|
|
||||||
<a href={`/${nextSlug}`} data-astro-prefetch>Newer</a>
|
|
||||||
)}
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
|
|||||||
@@ -38,26 +38,15 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* hide by default, i.e. if JS isn't enabled and the data-theme attribute didn't get set, */
|
/* hide by default, i.e. if JS isn't enabled and the data-theme attribute didn't get set, */
|
||||||
visibility: hidden;
|
display: none;
|
||||||
opacity: 0;
|
|
||||||
transition:
|
|
||||||
color 0.2s ease,
|
|
||||||
opacity 0.5s ease,
|
|
||||||
transform 0.5s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html[data-theme="light"]) button#switch-to-dark {
|
:global(html[data-theme="light"]) button#switch-to-dark {
|
||||||
opacity: 1;
|
display: block;
|
||||||
visibility: visible;
|
|
||||||
transform: rotate(360deg);
|
|
||||||
/* whichever one is currently active should be on top */
|
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html[data-theme="dark"]) button#switch-to-light {
|
:global(html[data-theme="dark"]) button#switch-to-light {
|
||||||
opacity: 1;
|
display: block;
|
||||||
visibility: visible;
|
|
||||||
transform: rotate(-360deg);
|
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
7
src/components/headings/H1.astro
Normal file
7
src/components/headings/H1.astro
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
import Heading from './Heading.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Heading tag="h1" {...Astro.props}>
|
||||||
|
<slot />
|
||||||
|
</Heading>
|
||||||
7
src/components/headings/H2.astro
Normal file
7
src/components/headings/H2.astro
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
import Heading from './Heading.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Heading tag="h2" {...Astro.props}>
|
||||||
|
<slot />
|
||||||
|
</Heading>
|
||||||
7
src/components/headings/H3.astro
Normal file
7
src/components/headings/H3.astro
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
import Heading from './Heading.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Heading tag="h3" {...Astro.props}>
|
||||||
|
<slot />
|
||||||
|
</Heading>
|
||||||
7
src/components/headings/H4.astro
Normal file
7
src/components/headings/H4.astro
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
import Heading from './Heading.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Heading tag="h4" {...Astro.props}>
|
||||||
|
<slot />
|
||||||
|
</Heading>
|
||||||
7
src/components/headings/H5.astro
Normal file
7
src/components/headings/H5.astro
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
import Heading from './Heading.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Heading tag="h5" {...Astro.props}>
|
||||||
|
<slot />
|
||||||
|
</Heading>
|
||||||
7
src/components/headings/H6.astro
Normal file
7
src/components/headings/H6.astro
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
import Heading from './Heading.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Heading tag="h6" {...Astro.props}>
|
||||||
|
<slot />
|
||||||
|
</Heading>
|
||||||
45
src/components/headings/Heading.astro
Normal file
45
src/components/headings/Heading.astro
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
import Icon from '@components/Icon.astro';
|
||||||
|
|
||||||
|
export interface Props extends astroHTML.JSX.HTMLAttributes {
|
||||||
|
tag: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tag: Tag, ...rest } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Tag {...rest}>
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
<a href={`#${Astro.props.id}`} class="deep-link" >
|
||||||
|
<Icon name="link" height="0.9em" width="0.9em" display="inline" />
|
||||||
|
</a>
|
||||||
|
</Tag>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover :global(svg) {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
& :global(svg) {
|
||||||
|
color: var(--content-color-faded);
|
||||||
|
padding-bottom: 0.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover :global(svg), &:active :global(svg) {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(min-width: 60rem) {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.1em;
|
||||||
|
left: -1.25em;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
src/components/headings/index.ts
Normal file
15
src/components/headings/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import H1 from './H1.astro';
|
||||||
|
import H2 from './H2.astro';
|
||||||
|
import H3 from './H3.astro';
|
||||||
|
import H4 from './H4.astro';
|
||||||
|
import H5 from './H5.astro';
|
||||||
|
import H6 from './H6.astro';
|
||||||
|
|
||||||
|
export const headingElements = {
|
||||||
|
h1: H1,
|
||||||
|
h2: H2,
|
||||||
|
h3: H3,
|
||||||
|
h4: H4,
|
||||||
|
h5: H5,
|
||||||
|
h6: H6,
|
||||||
|
};
|
||||||
3
src/components/icons/arrow-left.svg
Normal file
3
src/components/icons/arrow-left.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 219 B |
3
src/components/icons/arrow-right.svg
Normal file
3
src/components/icons/arrow-right.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 219 B |
3
src/components/icons/link.svg
Normal file
3
src/components/icons/link.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 353 B |
@@ -1,10 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<style>
|
|
||||||
svg {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 364 B |
@@ -2,6 +2,12 @@
|
|||||||
import '@styles/main.css';
|
import '@styles/main.css';
|
||||||
import '@fontsource-variable/baskervville-sc';
|
import '@fontsource-variable/baskervville-sc';
|
||||||
import ThemeSwitcher from '@components/ThemeSwitcher.astro';
|
import ThemeSwitcher from '@components/ThemeSwitcher.astro';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
pageTitle: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pageTitle } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -9,6 +15,8 @@ import ThemeSwitcher from '@components/ThemeSwitcher.astro';
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
|
||||||
|
<title>{pageTitle}</title>
|
||||||
|
|
||||||
<!-- avoid FOUC by setting the color schme here in the header -->
|
<!-- avoid FOUC by setting the color schme here in the header -->
|
||||||
<script>
|
<script>
|
||||||
const explicitPref = localStorage.getItem('theme-preference');
|
const explicitPref = localStorage.getItem('theme-preference');
|
||||||
|
|||||||
10
src/lib/content.ts
Normal file
10
src/lib/content.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
|
|
||||||
|
export async function listPosts() {
|
||||||
|
// all posts in dev, exlucde draft posts in prod
|
||||||
|
let entries = await getCollection('posts', ({ data }) => import.meta.env.DEV || !data.draft);
|
||||||
|
// sort by date descending
|
||||||
|
entries.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
---
|
---
|
||||||
import { getCollection } from 'astro:content';
|
import { listPosts } from '@lib/content.ts';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
import Post from '@components/Post.astro';
|
import Post from '@components/Post.astro';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const entries = await getCollection('posts');
|
const entries = await listPosts();
|
||||||
entries.sort((a, b) => a.data.date.getTime() - b.data.date.getTime())
|
|
||||||
|
|
||||||
// for each route, the page gets passed the entry itself, plus the previous and next slugs
|
// for each route, the page gets passed the entry itself, plus the previous and next slugs
|
||||||
// (if any), so that it can render links to them
|
// (if any), so that it can render links to them
|
||||||
return entries.map((entry, idx) => {
|
return entries.map((entry, idx) => {
|
||||||
const prevSlug = entries[idx - 1]?.id || null;
|
// entries are sorted in by date descending, so prev has a higher index and next has lower
|
||||||
const nextSlug = entries[idx + 1]?.id || null;
|
const prevSlug = entries[idx + 1]?.id || null;
|
||||||
|
const nextSlug = entries[idx - 1]?.id || null;
|
||||||
return {
|
return {
|
||||||
params: { slug: entry.id },
|
params: { slug: entry.id },
|
||||||
props: { entry, prevSlug, nextSlug },
|
props: { entry, prevSlug, nextSlug },
|
||||||
@@ -20,8 +20,9 @@ export async function getStaticPaths() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { entry, prevSlug, nextSlug } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout>
|
<BaseLayout pageTitle={entry.data.title}>
|
||||||
<Post {...Astro.props} />
|
<Post {entry} {prevSlug} {nextSlug} />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
24
src/pages/about.astro
Normal file
24
src/pages/about.astro
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout pageTitle="Joe's Blog - About">
|
||||||
|
<article class="prose">
|
||||||
|
<h1>About Me</h1>
|
||||||
|
|
||||||
|
<p>(Joe's wife wrote this because Joe feels weird writing about himself.)</p>
|
||||||
|
|
||||||
|
<p>Joe is a quirky, techy Tolkienite with a beautiful singing voice, an uncanny ability to do mental math, a bony, un-cuddleable frame, and a big mushy heart. He enjoys bike riding, computers, watching TV, reading about computers, playing Breath of the Wild, building computers, talking about something called "programming languages", and spending time with his family (which often involves fixing their computers). He graduated with a Liberal Arts degree from Thomas Aquinas College, the school of his forebears. He often remarks that he has greatly benefitted from the critical thinking skills he acquired at his alma mater in his current line of work.</p>
|
||||||
|
|
||||||
|
<p>He has spent, at the current time, about 2 years working on this blog. Most of his posts are about all of the work it took and everything he learned making this blog. Unlike most "bloggers", he has started with many blog posts and no blog, rather than a blog without posts. "Someday", he says, "I will actually get that blog up". I always nod encouragingly.</p>
|
||||||
|
|
||||||
|
<p>If you are reading this, then that day has arrived. We hope you enjoy it, and maybe even learn something along the way.</p>
|
||||||
|
</article>
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
article {
|
||||||
|
max-width: var(--content-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
---
|
---
|
||||||
|
import { listPosts } from '@lib/content.ts';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
|
import Post from '@components/Post.astro';
|
||||||
|
|
||||||
|
const entries = await listPosts();
|
||||||
|
// there will always be at leaste one entry
|
||||||
|
const entry = entries[0]!;
|
||||||
|
const prevSlug = entries[1] ? entries[1]?.id : null;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout>
|
<BaseLayout pageTitle={entry.data.title}>
|
||||||
<p>Index file</p>
|
<Post {entry} {prevSlug} />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
80
src/pages/posts.astro
Normal file
80
src/pages/posts.astro
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
import '@styles/prose.css';
|
||||||
|
|
||||||
|
import { render } from 'astro:content';
|
||||||
|
import { listPosts } from '@lib/content.ts';
|
||||||
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
|
|
||||||
|
// return all posts in dev, only non-draft in prod
|
||||||
|
const entries = await listPosts();
|
||||||
|
const posts = await Promise.all(entries.map(async entry => {
|
||||||
|
const { remarkPluginFrontmatter } = await render(entry);
|
||||||
|
return { ...entry, remarkPluginFrontmatter };
|
||||||
|
}));
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout pageTitle="Posts">
|
||||||
|
<div class="prose">
|
||||||
|
<h1>All Posts</h1>
|
||||||
|
<ul>
|
||||||
|
{posts.map( (p, idx) =>
|
||||||
|
<li>
|
||||||
|
{idx > 0 && <hr></hr>}
|
||||||
|
<h2>
|
||||||
|
<a href={`/${p.id}`} data-astro-prefetch>
|
||||||
|
{p.data.title}
|
||||||
|
</a>
|
||||||
|
{p.data.draft && <span class="draft-notice">Draft</span>}
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
{p.remarkPluginFrontmatter.description}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div.prose {
|
||||||
|
max-width: var(--content-width);
|
||||||
|
padding: var(--content-padding);
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
& h2 a {
|
||||||
|
color: var(--content-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
display: flex;
|
||||||
|
gap: 01rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-notice {
|
||||||
|
font-size: 0.5em;
|
||||||
|
font-family: 'Figtree Variable', sans-serif;
|
||||||
|
color: var(--nav-link-color);
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
padding: 0.05em 0.5em;
|
||||||
|
border-radius: 20% / 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
margin: 2.5rem 0;
|
||||||
|
background-color: var(--content-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
33
src/plugins.ts
Normal file
33
src/plugins.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Root, Paragraph, PhrasingContent } from 'mdast';
|
||||||
|
import type { VFile } from 'vfile';
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
import { toString } from 'mdast-util-to-string';
|
||||||
|
|
||||||
|
|
||||||
|
export function remarkDescription() {
|
||||||
|
return (tree: Root, vfile: VFile) => {
|
||||||
|
let description: string | null = null;
|
||||||
|
|
||||||
|
visit(tree, 'paragraph', (node: Paragraph) => {
|
||||||
|
if (description !== null) return;
|
||||||
|
description = summarize(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Astro exposes this as `remarkPluginFrontmatter` from render()
|
||||||
|
const astro = (vfile.data.astro ??= { frontmatter: {} }) as { frontmatter: Record<string, unknown> };
|
||||||
|
astro.frontmatter.description = description;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a paragraph node to a plain-text string, stripping any
|
||||||
|
* MDX JSX elements (e.g. <Sidenote>) so their content doesn't
|
||||||
|
* leak into the summary.
|
||||||
|
*/
|
||||||
|
function summarize(par: Paragraph): string {
|
||||||
|
const filtered = par.children.filter(
|
||||||
|
(child: PhrasingContent) => !(child.type as string).startsWith('mdxJsx')
|
||||||
|
);
|
||||||
|
return toString({ type: 'paragraph', children: filtered });
|
||||||
|
}
|
||||||
@@ -14,4 +14,11 @@ body {
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--link-color);
|
color: var(--link-color);
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
&:visited {
|
||||||
|
color: var(--link-color-visited);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
/* misc */
|
/* misc */
|
||||||
--heading-color: hsl(0deg 0% 27%);
|
--heading-color: hsl(0deg 0% 27%);
|
||||||
--link-color: var(--primary-color);
|
--link-color: var(--primary-color);
|
||||||
|
--link-color-visited: var(--accent-color-faded);
|
||||||
--nav-link-color: white;
|
--nav-link-color: white;
|
||||||
--neutral-gray: hsl(0deg 0% 30%);
|
--neutral-gray: hsl(0deg 0% 30%);
|
||||||
--inline-code-bg: hsl(0deg 0% 93%);
|
--inline-code-bg: hsl(0deg 0% 93%);
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
--dark-accent-color-faded: hsl(18deg 30% 45%);
|
--dark-accent-color-faded: hsl(18deg 30% 45%);
|
||||||
--dark-heading-color: hsl(35deg 25% 88%);
|
--dark-heading-color: hsl(35deg 25% 88%);
|
||||||
--dark-link-color: hsl(202deg 50% 50%);
|
--dark-link-color: hsl(202deg 50% 50%);
|
||||||
|
--dark-link-color-visited: hsl(270deg 33% 55%);
|
||||||
--dark-nav-link-color: var(--dark-heading-color);
|
--dark-nav-link-color: var(--dark-heading-color);
|
||||||
--dark-neutral-gray: hsl(220deg 10% 45%);
|
--dark-neutral-gray: hsl(220deg 10% 45%);
|
||||||
--dark-inline-code-bg: hsl(220deg 7.5% 21%);
|
--dark-inline-code-bg: hsl(220deg 7.5% 21%);
|
||||||
@@ -56,6 +58,7 @@
|
|||||||
--accent-color-faded: var(--accent-color-faded);
|
--accent-color-faded: var(--accent-color-faded);
|
||||||
--heading-color: var(--dark-heading-color);
|
--heading-color: var(--dark-heading-color);
|
||||||
--link-color: var(--dark-link-color);
|
--link-color: var(--dark-link-color);
|
||||||
|
--link-color-visited: var(--dark-link-color-visited);
|
||||||
--nav-link-color: var(--dark-nav-link-color);
|
--nav-link-color: var(--dark-nav-link-color);
|
||||||
--neutral-gray: var(--dark-neutral-gray);
|
--neutral-gray: var(--dark-neutral-gray);
|
||||||
--inline-code-bg: var(--dark-inline-code-bg);
|
--inline-code-bg: var(--dark-inline-code-bg);
|
||||||
@@ -74,6 +77,7 @@
|
|||||||
--accent-color-faded: var(--accent-color-faded);
|
--accent-color-faded: var(--accent-color-faded);
|
||||||
--heading-color: var(--dark-heading-color);
|
--heading-color: var(--dark-heading-color);
|
||||||
--link-color: var(--dark-link-color);
|
--link-color: var(--dark-link-color);
|
||||||
|
--link-color-visited: var(--dark-link-color-visited);
|
||||||
--nav-link-color: var(--dark-nav-link-color);
|
--nav-link-color: var(--dark-nav-link-color);
|
||||||
--neutral-gray: var(--dark-neutral-gray);
|
--neutral-gray: var(--dark-neutral-gray);
|
||||||
--inline-code-bg: var(--dark-inline-code-bg);
|
--inline-code-bg: var(--dark-inline-code-bg);
|
||||||
|
|||||||
Reference in New Issue
Block a user