finish css overhaul

This commit is contained in:
2023-12-26 20:30:09 -08:00
parent 9a85bef2be
commit ba4c2c2506
18 changed files with 1154 additions and 303 deletions

View File

@ -3,7 +3,6 @@
<head>
<meta charset="utf-8" />
<link rel="preload" href="/Tajawal-Regular.woff2" as="font" type="font/woff2" />
<link rel="preload" href="/Baskerville-Regular.woff2" as="font" type="font/woff2" />
<link rel="alternate" type="application/atom+xml" href="/feed">
<meta name="viewport" content="width=device-width" />
%sveltekit.head%

View File

@ -2,10 +2,10 @@
// Usage: <Dropcap word="Lorem">ipsum dolor sit amet...</Dropcap>
export let word;
const initial = word.slice(0, 1);
const initial = word.slice(0, 1).toUpperCase();
const remainder = word.slice(1);
// a few letters are narrower at the top, so we need more of a shift
// a few letters are narrower at the top, so we need to shift the remainder to compensate
const shiftValues = {
A: '-0.45em',
L: '-0.3em',
@ -16,16 +16,23 @@
</script>
<style>
.drop-cap {
text-transform: uppercase;
color: #8c0606;
/* box-sizing: border-box;*/
font-size: calc(var(--content-size) * var(--content-line-height) * 1.75);
float: left;
@font-face {
font-family: 'Baskerville';
line-height: 0.8;
margin-right: 0.1em;
font-style: normal;
font-weight: 400;
src: url(/Baskerville-Regular.woff2) format('woff2');
font-display: block;
}
.drop-cap {
display: block;
float: left;
margin-right: 0.1em;
color: var(--accent-color);
font-size: calc(var(--content-size) * 1.5 * 2);
line-height: 0.8;
font-family: 'Baskerville';
text-transform: uppercase;
}
.first-word {
@ -34,6 +41,10 @@
}
</style>
<svelte:head>
<link rel="preload" href="/Baskerville-Regular.woff2" as="font" type="font/woff2">
</svelte:head>
<span class="drop-cap">{initial}</span>
{#if remainder.length}

View File

@ -10,10 +10,10 @@
position: relative;
}
/* shift the anchor link to hang off the left side of the content when there's room */
// shift the anchor link to hang off the left side of the content when there's room
.anchor-wrapper {
/* slightly overlap the span with the heading so that it doesn't
lose its hover state as the cursor moves between them */
// slightly overlap the span with the heading so that it doesn't
// lose its hover state as the cursor moves between them
position: absolute;
padding-right: 0.5em;
left: -1.25em;
@ -24,10 +24,10 @@
}
a {
/* works better to set the size here for line-height reasons */
// works better to set the size here for line-height reasons
font-size: 0.9em;
/* give the anchor link a faded appearance by default */
// give the anchor link a faded appearance by default
color: hsl(0deg, 0%, 29%);
opacity: 40%;
transition: opacity 150ms, color 150ms;
@ -37,17 +37,17 @@
}
}
/* emphasize anchor link when heading is hovered or when clicked (the latter for mobile) */
// emphasize anchor link when heading is hovered or when clicked (the latter for mobile)
.h:hover a, .anchor-wrapper:hover a, .h a:active {
color: var(--accent-color);
opacity: 100%;
}
svg {
/* undo the reset that makes images block */
// undo the reset that makes images block
display: inline;
width: 1em;
/* tiny tweak for optical alignment */
// tiny tweak for optical alignment
transform: translateY(2px);
}
</style>

View File

@ -13,12 +13,13 @@
</script>
<script>
export let href; // we don't care about other attributes
export let href;
export let rel = '';
</script>
{#if href.startsWith('/') || host(href) === $page.host}
<a data-sveltekit-preload-data="hover" {href}>
<a data-sveltekit-preload-data="hover" {href} {rel}>
<slot></slot>
</a>
{:else}

View File

@ -14,7 +14,7 @@
<script>
export let title, date;
export let description = '';
export const draft = false;
export let draft = false;
export let toc = null;
export let slug;
@ -22,13 +22,13 @@
export let next = null;
</script>
<style>
<style lang="scss">
.page {
/* 3-column grid: left gutter, center content, and right gutter */
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, var(--content-width)) minmax(0, 1fr);
/* a bit of breathing room for narrow screens */
padding: 0 0.5rem;
padding: 0 var(--content-padding);
}
/* container for the table of contents */
@ -44,57 +44,56 @@
.subtitle {
font-size: 0.9em;
font-style: italic;
margin-top: -0.5rem;
margin-top: -0.75rem;
}
.post {
grid-column: 2 / 3;
}
.footer {
grid-column: 2 / 3;
margin-bottom: 2rem;
display: flex;
}
hr {
grid-column: 2 / 3;
width: 100%;
border-top: 1px solid hsl(0 0% 75%);
border-bottom: none;
margin: 2rem 0;
margin: 2.5rem 0;
}
.footer a {
/*display: flex;
align-items: center;
gap: 0.45em;*/
.footer {
grid-column: 2 / 3;
margin-bottom: 2.5rem;
display: flex;
font-size: 1rem;
color: var(--content-color-faded);
text-decoration: underline;
text-underline-offset: 0.25em;
text-decoration-color: transparent;
& a {
display: flex;
align-items: center;
gap: 0.45em;
transition: 150ms;
}
.footer a:hover {
text-decoration-color: currentColor;
text-decoration: underline;
}
font-size: 1.25rem;
color: var(--content-color-faded);
text-decoration: underline;
text-underline-offset: 0.25em;
text-decoration-color: transparent;
.footer svg {
width: 1em;
display: inline;
vertical-align: top;
transition: 150ms;
}
transition: 150ms;
a.prev:hover svg {
transform: translateX(-50%);
}
a.next:hover svg {
transform: translateX(50%);
&:hover {
text-decoration-color: currentColor;
text-decoration: underline;
}
}
& svg {
width: 1em;
transition: 150ms;
}
& .prev:hover svg {
transform: translateX(-50%);
}
& .next:hover svg {
transform: translateX(50%);
}
}
</style>
@ -105,9 +104,12 @@
<meta property="og:url" content="https://blog.jfmonty2.com/{slug}">
<meta property="og:description" content={description}>
<meta property="og:site_name" content="Joe's Blog">
<!-- Put this here for now, until I can get custom components working for codeblocks -->
<link rel="preload" href="/Hack-Regular.woff2" as="font" type="font/woff2">
</svelte:head>
<div class="page">
<div class="page prose">
<div class="title">
<h1 id="{makeSlug(title)}">{title}</h1>
<p class="subtitle">{formatDate(date)}</p>

View File

@ -1,5 +1,9 @@
<style>
/* always applicable */
<style lang="scss">
// minimum desirable sidenote width is 15rem, so breakpoint is
// content-width + 2(gap) + 2(15rem) + 2(scrollbar buffer)
$sidenote-breakpoint: 89.5rem;
// this has to be global because otherwise we can't target the body
:global(body) {
counter-reset: sidenote;
}
@ -7,124 +11,133 @@
.counter {
counter-increment: sidenote;
color: #444;
margin-left: 0.05rem;
}
.counter:after {
font-size: 0.75em;
position: relative;
bottom: 0.3rem;
color: #8c0606;
}
.sidenote {
color: #555;
font-size: 0.8rem;
}
.sidenote:before {
content: var(--sidenote-index, counter(sidenote)) " ";
/* absolute positioning puts it at the top-left corner of the sidenote, overlapping with the content
(because the sidenote is floated it counts as a positioned parent, I think) */
position: absolute;
/* translate moves it out to the left (and just a touch up to mimic the superscript efect)
-100% refers to the width of the element, so it pushes it out further if necessary (i.e. two digits instead of one) */
transform: translate(calc(-100% - 0.2rem), -0.15rem);
font-size: 0.75rem;
color: #8c0606;
margin-left: 0.065rem;
&::after {
font-size: 0.75em;
position: relative;
bottom: 0.375rem;
color: var(--accent-color);
content: counter(sidenote);
}
@media(max-width: $sidenote-breakpoint) {
&::after {
content: "[" counter(sidenote) "]";
}
&:hover::after {
color: var(--content-color);
cursor: pointer;
}
}
}
// hidden checkbox that tracks the state of the mobile sidenote
.sidenote-toggle {
display: none;
}
/* desktop display */
@media(min-width: 70em) {
.counter:after {
content: counter(sidenote);
}
.sidenote {
// anchor the counter, which is absolutely positioned
position: relative;
color: #555;
font-size: var(--content-size-sm);
hyphens: auto;
.sidenote {
--gap: 2rem;
--sidenote-width: min(16rem, calc(50vw - var(--gap) - 1rem - var(--content-width) / 2));
// desktop display, this can't coexist with mobile styling
@media(min-width: $sidenote-breakpoint) {
// max sidenote width is 20rem, if the window is too small then it's
// the width of the gutter, minus the gap between sidenote and gutter,
// minus an extra 1.5rem to account for the scrollbar on the right
--gap: 2.5rem;
--gutter-width: calc(50vw - var(--content-width) / 2);
--sidenote-width: min(
24rem,
calc(var(--gutter-width) - var(--gap) - 1.5rem)
);
width: var(--sidenote-width);
hyphens: auto;
position: relative;
float: right;
clear: right;
margin-right: calc(0rem - var(--sidenote-width) - var(--gap)); /* gives us 2rem of space between content and sidenote */
margin-bottom: 0.7rem;
margin-right: calc(-1 * var(--sidenote-width) - var(--gap));
margin-bottom: 0.75rem;
}
/* fade-in animation */
.sidenote {
opacity: 0;
animation: fade-in 600ms ease-out;
animation-delay: 500ms;
animation-fill-mode: forwards;
@media(max-width: $sidenote-breakpoint) {
position: fixed;
left: 0;
right: 0;
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;
// give us a horizontal buffer for the counter and dismiss button
--padding-x: calc(var(--content-padding) + 1.5rem);
padding: 1rem var(--padding-x);
background-color: white;
box-shadow: 0 -2px 4px -1px rgba(0, 0, 0, 0.06), 0 -2px 12px -2px rgba(0, 0, 0, 0.1);
// show the sidenote only when the corresponding checkbox is checked
transform: translateY(calc(100% + 2rem));
transition: transform 100ms;
.sidenote-toggle:checked + & {
transform: translateY(0);
}
}
@keyframes fade-in {
from {opacity: 0;}
to {opacity: 1;}
}
.sidenote-content {
max-width: var(--content-width);
margin: 0 auto;
&::before {
position: absolute;
transform: translateX(calc(-100% - 0.4em));
content: counter(sidenote);
color: var(--accent-color);
}
}
.dismiss {
display: block;
width: max-content;
margin: 0.5rem auto 0;
border-radius: 100%;
background: white;
border: 1px solid hsl(0deg, 0%, 75%);
box-shadow: 1px 1px 4px -1px rgba(0, 0, 0, 0.1);
padding: 0.25rem;
color: hsl(0deg, 0%, 50%);
&:hover, &:active {
color: var(--accent-color);
border: 1px solid var(--accent-color);
}
display: none;
@media(max-width: $sidenote-breakpoint) {
display: block;
}
cursor: pointer;
& label {
cursor: pointer;
}
& svg {
height: 1.5rem;
}
}
// nesting still needs work
@media(min-width: $sidenote-breakpoint) {
.nested.sidenote {
margin-right: 0;
margin-top: 0.7rem;
margin-bottom: 0;
}
.dismiss {
display: none;
}
}
/* mobile display */
@media (max-width: 70em) {
.counter:after {
content: "[" counter(sidenote) "]";
}
.counter:hover:after {
color: #000;
cursor: pointer;
}
.sidenote {
box-sizing: border-box;
position: fixed;
z-index: 1;
left: 0;
bottom: 0;
width: 100vw;
padding-top: 1rem;
padding-bottom: 1rem;
--pad: max(1rem, calc(50vw - var(--content-width) / 2));
padding-left: var(--pad);
padding-right: var(--pad);
background-color: #fff;
box-shadow: 0 -2px 4px -1px rgba(0, 0, 0, 0.06), 0 -2px 12px -2px rgba(0, 0, 0, 0.1);
display: none;
}
.sidenote-toggle:checked + .sidenote {
display: block;
}
.dismiss {
position: absolute;
right: 1.5rem;
top: -0.2rem;
font-size: 1.25rem;
color: #8c0606;
cursor: pointer;
}
.dismiss:hover {
transform: scale(1.1);
font-weight: 800;
}
}
</style>
@ -167,7 +180,17 @@
<label for={id} on:click={toggleState} class="counter"></label>
<input {id} bind:this={toggle} type="checkbox" class="sidenote-toggle" />
<span class="sidenote" class:nested bind:this={noteBody}>
<label class="dismiss" for={id} on:click={toggleState}>&times;</label>
<slot></slot>
</span>
<!-- outer element so that on mobile it can extend the whole width of the viewport -->
<div class="sidenote" class:nested bind:this={noteBody}>
<!-- inner element so that content can be centered -->
<div class="sidenote-content">
<slot></slot>
<button class="dismiss" on:click={toggleState}>
<label for={id}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</label>
</button>
</div>
</div>

View File

@ -26,6 +26,21 @@
}
}
function ellipsize(text) {
return text;
// not sure about this, decide on it later
// if (text.length > 40) {
// text = text.slice(0, 40);
// // looks weird when we have an ellipsis following a space
// if (text.slice(-1) === ' ') {
// text = text.slice(0, -1);
// }
// return text + '…';
// }
// return text;
}
onMount (() => {
// These shouldn't change over the life of the page, so we can cache them
headings = Array.from(document.querySelectorAll('h2[id], h3[id]'));
@ -35,33 +50,26 @@
<svelte:window on:scroll={setCurrentHeading} />
<style>
<style lang="scss">
#toc {
display: none;
position: sticky;
top: 1.5rem;
margin-left: 1rem;
margin-right: 2rem;
margin-right: 2.5rem;
max-width: 14rem;
max-width: 18rem;
color: var(--content-color-faded);
opacity: 0;
animation: fade-in 600ms ease-out;
animation-delay: 500ms;
animation-fill-mode: forwards;
// minimum desirable TOC width is 8rem
// add 3rem for margins, giving total gutter width of 11.5rem
// multiply by 2 since there are two equally-sized gutters, then add content-width (52.5rem)
@media(max-width: 75.5rem) {
display: none;
}
}
@media(min-width: 1300px) {
#toc { display: block }
}
@keyframes fade-in {
from {opacity: 0}
to {opacity: 1}
}
/* margin-left is to match the padding on the top-level list items,
but here it needs to be margin so that the border is also shifted */
// margin-left is to match the padding on the top-level list items,
// but here it needs to be margin so that the border is also shifted
h5 {
font-variant: petite-caps;
font-weight: 500;
@ -72,7 +80,7 @@
padding-bottom: 0.25em;
border-bottom: 1px solid currentcolor;
/* make the border stretch beyond the text just a bit, because I like the effect */
// make the border stretch beyond the text just a bit, because I like the effect
padding-right: 1.5rem;
}
@ -84,42 +92,51 @@
li {
position: relative;
font-size: 0.9rem;
}
li.depth-2 {
align-items: stretch;
margin-bottom: 0.2rem;
}
li.depth-3 {
align-items: center;
margin-bottom: 0.05rem;
margin-top: 0.45em;
font-size: var(--content-size-sm);
// make sure that one item wrapped across multiple lines doesn't just looke like multiple items
line-height: 1.1;
&.depth-2 {
align-items: stretch;
margin-bottom: 0.2rem;
}
&.depth-3 {
align-items: center;
margin-bottom: 0.05rem;
}
&.current, &:hover {
color: var(--content-color);
}
}
.marker {
position: absolute;
left: -0.6rem;
}
.bar {
width: 0.1rem;
height: 100%;
}
.dot {
width: 0.15rem;
height: 0.15rem;
border-radius: 50%;
/* vertically center within its containing block */
top: 0;
bottom: 0;
margin: auto 0;
}
li.current, li:hover {
color: var(--content-color);
}
.current .marker, li:hover .marker {
background-color: var(--accent-color);
.current &, li:hover & {
background-color: var(--accent-color);
}
&.bar {
width: 0.125rem;
height: 100%;
}
&.dot {
width: 0.2rem;
height: 0.2rem;
border-radius: 50%;
// vertically center within its containing block
top: 0;
bottom: 0;
margin: auto 0;
}
}
// default link styling messes everything up again
a {
color: inherit;
text-decoration: none;
@ -136,12 +153,12 @@
{#if item.depth === 2}
<li class="depth-2" class:current={item.slug === currentHeadingSlug} style:align-items="stretch">
<span class="marker bar"></span>
<a href="#{item.slug}">{item.text}</a>
<a href="#{item.slug}">{ellipsize(item.text)}</a>
</li>
{:else if item.depth === 3}
<li class="depth-3" class:current={item.slug === currentSubheadingSlug} style:align-items="center" style:margin-left="0.75em">
<span class="marker dot"></span>
<a href="#{item.slug}">{item.text}</a>
<a href="#{item.slug}">{ellipsize(item.text)}</a>
</li>
{/if}
{/each}

View File

@ -3,10 +3,11 @@ import { find } from 'unist-util-find';
import { toText } from 'hast-util-to-text';
import { makeSlug } from '../lib/utils.js';
import {writeFileSync} from 'node:fs';
import {toHtml} from 'hast-util-to-html';
export function localRehype() {
let printed = false;
return (tree, vfile) => {
const needsDropcap = vfile.data.fm.dropcap !== false
let dropcapAdded = false;

View File

@ -1,26 +1,29 @@
<style>
<style lang="scss">
.header {
background-color: #4f5f68;
background: hsl(202deg 14% 36%);
}
nav {
max-width: 30rem;
margin: 0 auto;
display: flex;
justify-content: space-around;
}
nav a {
color: white;
width: 8rem;
min-width: 6rem;
font-size: 1.5rem;
text-decoration: none;
text-align: center;
padding: 0.25rem 0;
}
nav a:hover {
background-color: #00000025;
display: flex;
justify-content: space-between;
& a {
flex: 1;
max-width: 8rem;
padding: 0.25rem 1rem;
font-size: 1.75rem;
color: white;
text-decoration: none;
text-align: center;
&:hover {
background: hsl(0deg 0% 0% / 10%);
}
}
}
</style>

View File

@ -20,4 +20,4 @@ A lot of time has passed since then, and now I can easily imagine situations whe
## CLI apps are easier to write
blah blah words here

View File

@ -1,3 +1,7 @@
<script>
import '$styles/prose.scss';
</script>
<style>
.content {
max-width: var(--content-width);
@ -10,7 +14,7 @@
</svelte:head>
<div class="content">
<div class="prose content">
<h1>About Me</h1>
<p>(Joe's wife wrote this because Joe feels weird writing about himself.)</p>

View File

@ -1,75 +1,86 @@
<script>
import '$styles/prose.scss';
import { formatDate } from '$lib/datefmt.js';
import { postData } from '../_posts/all.js';
</script>
<style>
#posts {
/*text-align: center;*/
<style lang="scss">
.wrapper {
padding: 0 var(--content-padding);
}
.posts {
max-width: var(--content-width);
margin: 0 auto;
}
hr {
margin: 2rem 0;
margin: 2.5rem 0;
border-color: #eee;
}
.post-date {
color: #808080;
color: var(--content-color-faded);
}
.draft-notice {
vertical-align: 0.3rem;
font-size: 0.6rem;
vertical-align: middle;
font-size: 0.75rem;
padding: 0 0.3rem;
color: #e00;
background-color: #ffd9d9;
border: 1px solid red;
border-radius: 20%/50%;
margin: 0 0.2rem;
border-radius: 20% / 50%;
}
.post-link {
text-decoration: none;
}
.post-link:hover {
text-decoration: underline;
&:hover {
text-decoration: underline;
}
}
h2 {
font-size: 1.25rem;
display: flex;
align-items: center;
gap: 0.75rem;
margin-top: 0.5rem;
margin-bottom: 0.75rem;
font-size: 1.5rem;
& a {
color: currentcolor;
}
}
h2 a {
color: currentcolor;
}
</style>
<svelte:head>
<title>Posts</title>
</svelte:head>
<div id="posts">
<h1 style:text-align="center">All Posts</h1>
{#each postData as post, idx}
<div class="post">
<div class="post-date">{new Date(post.date).toISOString().split('T')[0]}</div>
<h2>
<a data-sveltekit-preload-data="hover" class="post-link" href="/{post.slug}">
{post.title}
</a>
{#if post.draft}
<span class="draft-notice">Draft</span>
{/if}
</h2>
<p>{post.description}</p>
</div>
<div class="wrapper">
<div class="posts prose">
<h1 style:text-align="center">All Posts</h1>
{#each postData as post, idx}
<div class="post">
<div class="post-date">{new Date(post.date).toISOString().split('T')[0]}</div>
<h2 class="prose">
<a data-sveltekit-preload-data="hover" class="post-link" href="/{post.slug}">
{post.title}
</a>
{#if post.draft}
<span class="draft-notice">Draft</span>
{/if}
</h2>
<p>{post.description}</p>
</div>
{#if idx < postData.length - 1}
<hr>
{/if}
{/each}
{#if idx < postData.length - 1}
<hr>
{/if}
{/each}
</div>
</div>

View File

@ -17,6 +17,10 @@ code {
font-family: 'Hack', monospace;
}
pre[class*="language-"] {
line-height: 1.25;
}
pre > code[class*="language-"] {
font-size: 0.75em;
font-family: 'Hack', monospace;

View File

@ -11,8 +11,10 @@
:root {
--content-size: 1.25rem;
--content-line-height: 1.3;
--content-size-sm: 1rem;
// --content-line-height: 1.3;
--content-width: 52.5rem;
--content-padding: 0.65rem;
--content-color: #1e1e1e;
--content-color-faded: #555;
--accent-color: hsl(0deg, 92%, 29%);
@ -21,7 +23,7 @@
body {
font-family: 'Tajawal', sans-serif;
font-size: var(--content-size);
line-height: var(--content-line-height);
// line-height: var(--content-line-height);
letter-spacing: -0.005em;
color: var(--content-color);
}

View File

@ -1,39 +1,33 @@
@font-face {
font-family: 'Baskerville';
font-style: normal;
font-weight: 400;
src: url(/Baskerville-Regular.woff2) format('woff2');
font-display: block;
}
.prose {
h1, h2, h3, h4, h5, h6 {
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, Arial, sans-serif;
font-weight: 600;
color: #464646;
}
h1, h2, h3, h4, h5, h6 {
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, Arial, sans-serif;
font-weight: 600;
color: #464646;
}
h1 {
margin-top: 0.5em;
font-size: 2em;
font-variant: petite-caps;
}
h1 {
margin-top: 0.5em;
font-size: 2em;
font-variant: petite-caps;
}
h2 {
font-size: 1.5em;
}
h2 {
font-size: 1.5em;
}
h3 {
font-size: 1.2em;
}
h3 {
font-size: 1.2em;
}
h4 {
font-size: 1.1em;
}
h4 {
font-size: 1.1em;
}
h1, h2, h3, h4 {
margin-bottom: 0.5em;
}
h1, h2, h3, h4 {
margin-bottom: 0.5em;
}
p {
margin-bottom: 0.8em;
p {
margin-bottom: 0.8em;
}
}