book previews

This commit is contained in:
Joseph Montanaro 2024-01-08 23:05:44 -08:00
parent 816f3a9c0f
commit 918791baf6
12 changed files with 227 additions and 13 deletions

9
src/lib/Codeblock.svelte Normal file
View File

@ -0,0 +1,9 @@
<script>
let classes = '';
export {classes as class};
</script>
<p>Hello world!</p>
<pre class={classes}>
<slot></slot>
</pre>

View File

@ -70,7 +70,7 @@
bottom: 0;
// since headings have relative position, any that come after
// the current sidenote in the DOM get stacked on top by default
z-index: 1;
z-index: 10;
// give us a horizontal buffer for the counter and dismiss button
--padding-x: calc(var(--content-padding) + 1.5rem);
@ -80,9 +80,13 @@
// show the sidenote only when the corresponding checkbox is checked
transform: translateY(calc(100% + 2rem));
transition: transform 100ms;
transition: transform 125ms;
// when moving from shown -> hidden, ease-in
transition-timing-function: ease-in;
.sidenote-toggle:checked + & {
transform: translateY(0);
// when moving hidden -> shown, ease-out
transition-timing-function: ease-out;
}
}
}

View File

@ -55,15 +55,15 @@
position: sticky;
top: 1.5rem;
margin-left: 1rem;
margin-right: 2.5rem;
margin-right: 4rem;
max-width: 18rem;
color: var(--content-color-faded);
// minimum desirable TOC width is 8rem
// add 3rem for margins, giving total gutter width of 11.5rem
// add 4rem for margins, giving total gutter width of 12.5rem
// multiply by 2 since there are two equally-sized gutters, then add content-width (52.5rem)
@media(max-width: 75.5rem) {
@media(max-width: 77.5rem) {
display: none;
}
}

View File

@ -0,0 +1,169 @@
<script context="module">
import { writable } from 'svelte/store';
let activePreview = writable(null);
</script>
<script>
import { tick } from 'svelte';
import data from './books.json';
const images = import.meta.glob('./images/*.jpg', {eager: true});
export let ref;
const {type, title, author, description, url} = data[ref];
const imageUrl = images[`./images/${ref}.jpg`].default;
$: visible = $activePreview === ref;
let mousePresent = false;
let offset, popover;
async function show() {
$activePreview = ref;
mousePresent = true;
await tick();
const rect = popover.getBoundingClientRect();
// 12px is approximately var(--content-padding)
if (rect.x < 12) {
offset = `${12 - rect.x}px`;
}
}
function hide() {
mousePresent = false;
// mouseenter fires when the mouse moves into the floating div as well,
// so this gives us a "grace period" that applies to either anchor or popover
setTimeout(
() => {
if (!mousePresent && $activePreview === ref) {
$activePreview = null;
}
},
300
);
}
function clickLink(evt) {
// if click happened without hover, then we must be on mobile
if (!visible) {
$activePreview = ref;
evt.preventDefault();
}
// if visible, but mouse is not present, also mobile
else if (visible && !mousePresent) {
$activePreview = null;
evt.preventDefault();
}
}
let detailsLink;
function blurLink(evt) {
// do this in the next task, in case the click was inside the popover
setTimeout(
() => {
// check this here in case it got changed by a different event handler
if ($activePreview == ref) {
$activePreview = null;
}
},
0
)
// if ($activePreview == ref) {
// setTimeout(() => $activePreview = null, 0);
// }
}
</script>
<style lang="scss">
.base {
position: relative;
// on mobile, we want the popover's position to be calculated
// relative to the whole document, not the link text
@media(max-width: 27rem) {
position: static;
}
}
.popover {
position: absolute;
// popover should float above the link text by a bit
bottom: calc(100% + 0.5rem);
// and be centered relative to the link, unless that would put it off screen
left: 50%;
transform: translateX(
calc(-50% + var(--offset, 0px))
);
@media(max-width: 27rem) {
// bounding box is now the whole document
// we want to start from its initial vertical position
bottom: unset;
// center it horizontally, with some space on the sides
width: unset;
left: 1rem;
right: 1rem;
margin-left: auto;
margin-right: auto;
// and move it back up so it's above the text again
transform: translateY(
calc(-100% - 1.5em - 0.5rem)
);
}
// visibility is controlled by the .visible class
display: none;
&.visible {
display: flex;
}
// two-column layout, one for image and one for text
gap: 1rem;
width: 25rem;
height: 192px;
overflow-y: auto;
padding: 0.35rem;
background: white;
box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.1);
border: 1px solid var(--content-color);
z-index: 1;
font-size: var(--content-size-sm);
}
img {
height: 100%;
// sticky position ensures that the image stays visible when we scroll the text
position: sticky;
top: 0;
}
a.details {
color: var(--primary-color);
&:visited {
color: var(--accent-color);
}
}
a:active {
color: var(--accent-color);
}
</style>
<span class="base" on:mouseenter={show} on:mouseleave={hide}><!-- get rid of whitespace
--><a href={url} target="_blank" on:click={clickLink} on:blur={blurLink}>
<slot></slot><!--
--></a><!--
--><div class="popover" bind:this={popover} class:visible style:--offset={offset}>
<img src={imageUrl}>
<div>
<h4>{title}</h4>
<p>
{description}
<a class="details" href={url} target="_blank" bind:this={detailsLink}>More</a>
</p>
</div>
</div><!--
--></span>

View File

@ -0,0 +1,23 @@
{
"lotr": {
"type": "trilogy",
"title": "The Lord of the Rings",
"author": "J. R. R. Tolkien",
"description": "Epic fantasy trilogy written by Oxford professor and linguist J. R. R. Tolkien. Considered by many to be the major trend-setter for the modern fantasy genre.",
"url": "https://www.goodreads.com/series/66175-the-lord-of-the-rings"
},
"neverwhere": {
"type": "book",
"title": "Neverwhere",
"author": "Neil Gaiman",
"description": "Under the streets of London there's a world most people could never dream of. A city of monsters and saints, murderers and angels, knights in armour and pale girls in black velvet. \"Neverwhere\" is the London of the people who have fallen between the cracks.",
"url": "https://www.goodreads.com/book/show/14497.Neverwhere"
},
"earthsea": {
"type": "series",
"title": "Earthsea Cycle",
"author": "Ursula K. Le Guin",
"description": "Series of high fantasy stories set in an archipelago world where names are power and dragons roam the skies.",
"url": "https://www.goodreads.com/series/40909-earthsea-cycle"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -1,6 +1,6 @@
<style lang="scss">
.header {
background: hsl(202deg 14% 36%);
background: var(--primary-color-faded);
}
nav {

View File

@ -6,15 +6,16 @@ draft: true
<script>
import Sidenote from '$lib/Sidenote.svelte';
import BookPreview from '$projects/fantasy/BookPreview.svelte';
</script>
For a while now, I've had a private taxonomy of fantasy books, based on the distinction (or lack thereof) between the fantasy world and our own. It goes something like this:
* *High Fantasy* is set in a world completely separate from our own, with no passage from one to the other. At the most, there might be faint hints that the fantasy world represents ours in the distant past (see LOTR).
* *High Fantasy* is set in a world completely separate from our own, with no passage from one to the other. At the most, there might be faint hints that the fantasy world represents ours in the distant past.
* *Low Fantasy* is set in a fantasy world that is separate from ours, but that can be reached (at least some of the time) by some means, such as a portal, magic object, ritual, etc.
* *Urban Fantasy* is fantasy in which the fantasy world is contained _within_ our world, but typically hidden from the prying eyes of mere mortals in some fashion.
I refer to this as a "personal" taxonomy because as far as I can tell, nobody else shares it. The terms are well-known, of course, and there's some overlap - what most people call "High Fantasy" certainly does tend to be set in fantasy worlds with no connection to our own - but "Urban Fantasy" in particular means a whole lot more to most people than just "fantasy set in our world." I imagine that most people would agree that urban fantasy should be, well, urban - not that it has to take place in _cities_ stricly speaking, but it should at least portray the fantastical elements in and around the real world, and pay some attention to the question of how it stays out of sight of Muggles.
I refer to this as a "personal" taxonomy because as far as I can tell, nobody else shares it. The terms are well-known, of course, and there's some overlap--what most people call "High Fantasy" certainly does tend to be set in fantasy worlds with no connection to our own - but "Urban Fantasy" in particular means a whole lot more to most people than just "fantasy set in our world." I imagine that most people would agree that urban fantasy should be, well, urban - not that it has to take place in _cities_ stricly speaking, but it should at least portray the fantastical elements in and around the real world, and pay some attention to the question of how it stays out of sight of Muggles.
Obviously, my personal classification system is much simpler and stricter than this. To be honest, it's not terribly useful on its own - while the relationship between a fantasy world and our own is certainly _an_ attribute worth considering for classification purposes, it's far from the only one.
@ -27,7 +28,7 @@ This means that the ideal basis for an axis should be:
* Objective: As much as possible, at least. Our existing axis of world overlap does well on this metric: it's usually pretty clear where a given story should fall. Sure, there are a few cases where different people might disagree about which of two stories has more or less overlap, but only when they have a very similar amount of overlap to start with. It doesn't seem likely that different people could end up placing the same story on opposite ends of the axis.
* Impactful: A story's position on the axis should go at least some way toward determining what kind of story it is. For example, the climate of the fantasy world would _not_ do well on this metric, since it doesn't matter a lot whether the world is hot or cold when you're asking how it should be classified.<Sidenote>The most impact I can imagine a fantasy world's climate having is something like the situation in A Song of Ice and Fire, where the extremely-long cycle of seasons (each cycle takes decades, if I recall correctly) lead to political differences because e.g. people younger than a certain age have never experienced a winter. But even then, it isn't the climate itself that most people would base their classification on, it's the political situation. It still seems largely incidental, _for classification purposes_, that the political complexity comes partly from environmental factors.</Sidenote>
Okay, so what different axes can we come up with?
Okay, so what different axes can we come up with? Obviously we can start with the original one that I wanted to base my taxonomy on:
## World overlap
@ -39,7 +40,7 @@ Notable subregions include:
No overlap at all. I think most fantasy that's written tends to fall here. At least, it's what most people think of when you say "fantasy book," and the Wikipedia definition of "Fantasy" specifies that it's "typically set in a fictional universe," so I think it's fair to say that this is the "standard" position for a fantasy story to occupy on this axis.
Examples: _Earthsea_, _The Prydain Chronicles_, _Wheel of Time_, _Belgariad_, _A Song of Ice and Fire_, etc. Pick up a book from the fantasy section of a bookstore and there's at least a 50% chance it will fall into this category.
Examples: <BookPreview ref="lotr">_The Lord of the Rings_</BookPreview>, <BookPreview ref="earthsea">_Earthsea_</BookPreview>, _The Prydain Chronicles_, _Wheel of Time_, _Belgariad_, _A Song of Ice and Fire_, etc. Pick up a book from the fantasy section of a bookstore and there's at least a 50% chance it will fall into this category.
### Mythopoeic Fantasy
@ -74,9 +75,11 @@ Fantasy that's set in our world, but with magic.<Sidenote>Or other fantastical e
In the end, though, I decided that the point of this axis is to classify fantasy according to how much the fantasy world overlaps with our own, and alternate history involves _quite a lot of overlap_, even though the end result is a world that's not _quite_ identical with the real world.
An interesting quirk of alternate-history fantasy is that it's frequently set significantly in the past, but for some reason not _quite_ as far back as the quasi-Medieval era that is the bread and butter of most "standard" fantasy. The Napoleonic/Regency era is popular, as is the Victorian era, but the only modern-day alternate history stories I can think of are the _Bartimaeus_ trilogy and _Unsong_.
Broadly speaking there are two variants of alternate history: 1) Either the fantastic has always been a part of life, or 2) it was suddenly introduced into the world by some (usually fairly cataclysmic) event.
Other examples of this type of story include: the _Elemental Masters_ series, the _Cecelia and Kate_ books, the _Temeraire_ books, _Jonathan Strange & Mr Norrell_, the _Bartimaeus_ trilogy, and many more.
Examples of the first variant include _Cecelia and Kate_, the _Temeraire_ books, _Jonathan Strange and Mr Norrell_, etc. An interesting quirk of this variant is that it's almost always set significantly in the past, but for some reason not _quite_ as far back as the quasi-Medieval era that is the bread and butter of most "standard" fantasy. The Napoleonic/Regency era is popular, as is the Victorian era. Modern-day alternate-history stories of this type seem fairly uncommon--_Bartimaeus_ is the only example I can think of off the top of my head.
Examples of the secont variant include _Unsong_, _Reckoners_, and _The Tapestry_. Stories of this type are much more commonly set in the modern day--understandably so, since "what would happen to society if magic were suddenly introduced" is a pretty interesting question to explore.
### Urban Fantasy
@ -89,3 +92,6 @@ So urban fantasy depicts a world where there's magic<Sidenote>Or dragons, or fai
For the most part, though, the people who know about magic are the people who have magic, plus the occasional Ascended Muggle Sidekick who's there for flavor (and to act as an audience surrogate, probably.) In fact, quite frequently the main conflict of the story is about _preventing_ the magical part of the world from being exposed, either because the magicians are afraid that a world full of angry normies would actually pose a threat to them<Sidenote>In this case the Salem witch trials and similar events are frequently invoked, in-universe as cautionary tales of what might happen "if _they_ find out about us."</Sidenote>, or because the wise and benevolent Wizards' Council has declared that even though they _could_ rule the world, it wouldn't be fair to the poor normies.
Other notable examples of the genre include _The Dresden Files_, _Percy Jackson and the Olympians_, _The Laundry Files_, _Neverwhere_, _American Gods_, the _Artemis Fowl_ books, the _Mither Mages_ series, the _Iron Druid_ series, _Monster Hunter International_, and of course _Harry Potter_.

View File

@ -17,6 +17,8 @@
--content-padding: 0.65rem;
--content-color: #1e1e1e;
--content-color-faded: #555;
--primary-color: hsl(202deg 72% 28%);
--primary-color-faded: hsl(202deg 14% 36%);
--accent-color: hsl(0deg, 92%, 29%);
--accent-color-faded: hsl(0deg, 25%, 55%);
}

View File

@ -12,7 +12,7 @@
// Allow percentage-based heights in the application
html, body {
height: 100%;
min-height: 100%;
}
// Improve media defaults

View File

@ -25,6 +25,7 @@ const config = {
adapter: staticAdapter(),
alias: {
'$styles': 'src/styles',
'$projects': 'src/projects',
}
}
};