mirror of
https://github.com/jaronpate/folio.git
synced 2026-10-18 17:06:56 +00:00
rework to 2000's-ish style site
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import '~/assets/css/main.css';
|
||||
import {
|
||||
DEFAULT_DESCRIPTION,
|
||||
DEFAULT_IMAGE,
|
||||
@@ -41,8 +40,6 @@ useHead({
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
+98
-133
@@ -1,13 +1,22 @@
|
||||
/* Sohne Buch */
|
||||
/*@font-face {
|
||||
font-family: 'SohneBuch';
|
||||
src:
|
||||
url('~/assets/fonts/Sohne/Sohne-Buch.woff') format('woff'),
|
||||
url('~/assets/fonts/Sohne/Sohne-Buch.ttf') format('truetype');
|
||||
font-display: swap;
|
||||
}*/
|
||||
|
||||
/* Plantin MT Pro */
|
||||
@font-face {
|
||||
font-family: 'Plantin MT Pro';
|
||||
src: url('~/assets/fonts/Plantin MT Pro/PlantinMTProLight.TTF')
|
||||
format('truetype');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Plantin MT Pro';
|
||||
src: url('~/assets/fonts/Plantin MT Pro/PlantinMTProLightIt.TTF')
|
||||
format('truetype');
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Plantin MT Pro';
|
||||
src: url('~/assets/fonts/Plantin MT Pro/PlantinMTProRg.TTF')
|
||||
@@ -46,18 +55,73 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Plantin MT Pro';
|
||||
src: url('~/assets/fonts/Plantin MT Pro/PlantinMTProLight.TTF')
|
||||
src: url('~/assets/fonts/Plantin MT Pro/PlantinMTProSmBd.TTF')
|
||||
format('truetype');
|
||||
font-weight: 300;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Plantin MT Pro';
|
||||
src: url('~/assets/fonts/Plantin MT Pro/PlantinMTProLightIt.TTF')
|
||||
src: url('~/assets/fonts/Plantin MT Pro/PlantinMTProSemiBdIt.TTF')
|
||||
format('truetype');
|
||||
font-weight: 300;
|
||||
font-weight: 600;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Plantin MT Pro';
|
||||
src: url('~/assets/fonts/Plantin MT Pro/PlantinMTProCnBd.TTF')
|
||||
format('truetype');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-stretch: condensed;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Sohne Buch */
|
||||
@font-face {
|
||||
font-family: 'SohneBuch';
|
||||
src:
|
||||
url('~/assets/fonts/Sohne/Sohne-Buch.woff') format('woff'),
|
||||
url('~/assets/fonts/Sohne/Sohne-Buch.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Alegreya */
|
||||
@font-face {
|
||||
font-family: 'Alegreya';
|
||||
src: url('~/assets/fonts/Alegreya HT/Alegreya-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Alegreya';
|
||||
src: url('~/assets/fonts/Alegreya HT/Alegreya-Italic.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Alegreya';
|
||||
src: url('~/assets/fonts/Alegreya HT/Alegreya-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Alegreya';
|
||||
src: url('~/assets/fonts/Alegreya HT/Alegreya-BoldItalic.woff2')
|
||||
format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -70,13 +134,7 @@
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction';
|
||||
src: url('~/assets/fonts/Redaction/Redaction-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Redaction';
|
||||
src: url('~/assets/fonts/Redaction/Redaction-Italic.woff2') format('woff2');
|
||||
@@ -85,57 +143,14 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Redaction 10 */
|
||||
@font-face {
|
||||
font-family: 'Redaction 10';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_10-Regular.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 10';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_10-Bold.woff2') format('woff2');
|
||||
font-family: 'Redaction';
|
||||
src: url('~/assets/fonts/Redaction/Redaction-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 10';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_10-Italic.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Redaction 20 */
|
||||
@font-face {
|
||||
font-family: 'Redaction 20';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_20-Regular.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 20';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_20-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 20';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_20-Italic.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Redaction 35 */
|
||||
@font-face {
|
||||
font-family: 'Redaction 35';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_35-Regular.woff2')
|
||||
@@ -144,13 +159,7 @@
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 35';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_35-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Redaction 35';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_35-Italic.woff2')
|
||||
@@ -160,80 +169,36 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Redaction 50 */
|
||||
@font-face {
|
||||
font-family: 'Redaction 50';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_50-Regular.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 50';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_50-Bold.woff2') format('woff2');
|
||||
font-family: 'Redaction 35';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_35-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Space Mono */
|
||||
@font-face {
|
||||
font-family: 'Redaction 50';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_50-Italic.woff2')
|
||||
format('woff2');
|
||||
font-family: 'Space Mono';
|
||||
src: url('~/assets/fonts/Space Mono/SpaceMono-Regular.ttf')
|
||||
format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
src: url('~/assets/fonts/Space Mono/SpaceMono-Italic.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Redaction 70 */
|
||||
@font-face {
|
||||
font-family: 'Redaction 70';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_70-Regular.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 70';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_70-Bold.woff2') format('woff2');
|
||||
font-family: 'Space Mono';
|
||||
src: url('~/assets/fonts/Space Mono/SpaceMono-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 70';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_70-Italic.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Redaction 100 */
|
||||
@font-face {
|
||||
font-family: 'Redaction 100';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_100-Regular.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 100';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_100-Bold.woff2')
|
||||
format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Redaction 100';
|
||||
src: url('~/assets/fonts/Redaction/Redaction_100-Italic.woff2')
|
||||
format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/*@import url('https://fonts.googleapis.com/css2?family=Young+Serif&display=swap');*/
|
||||
|
||||
+310
-95
@@ -1,163 +1,378 @@
|
||||
@import './fonts.css';
|
||||
|
||||
:root {
|
||||
/*--font: 'Baskervville', serif;*/
|
||||
/*--font: 'EB Garamond', serif;*/
|
||||
--font: 'Plantin MT Pro', serif;
|
||||
/*--font: 'Young Serif', serif;*/
|
||||
/*--font: 'SohneBuch', sans-serif;*/
|
||||
/*--font-plain: 'SohneBuch', sans-serif;*/
|
||||
/*--font: 'Redaction', serif;*/
|
||||
/*--font-hover: 'Redaction 70', serif;*/
|
||||
--font-plain: var(--font);
|
||||
--font-hover: var(--font);
|
||||
--text: #333;
|
||||
--muted: #666;
|
||||
--font: 'Plantin MT Pro', Georgia, serif;
|
||||
--font-plain: 'SohneBuch', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-mono: 'Space Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
|
||||
/* paper + ink from the old site; accent/navy sampled from blend.jpeg */
|
||||
--bg: #f6f7f9;
|
||||
--bg-secondary: #e9ebef;
|
||||
--dot-color: #666;
|
||||
|
||||
--color-white-500: #f6f7f9;
|
||||
--text: #2e3140;
|
||||
--muted: #6a6d7a;
|
||||
--accent: #c45d52;
|
||||
--accent-soft: #e78c88;
|
||||
--navy: #2a2c61;
|
||||
--hairline: #d8dbe2;
|
||||
--code-bg: #f3e4e2;
|
||||
--img-matte: #e9ebef;
|
||||
|
||||
--body-weight: 300;
|
||||
--measure: 38rem;
|
||||
--note-width: 13rem;
|
||||
--note-gap: 2rem;
|
||||
--page-pad: 1.25rem;
|
||||
--col: min(var(--measure), calc(100vw - var(--page-pad) * 2));
|
||||
--cluster: calc(var(--col) + var(--note-gap) + var(--note-width));
|
||||
--col-left: max(var(--page-pad), calc((100vw - var(--cluster)) / 2));
|
||||
--gap: 1rem;
|
||||
|
||||
--radius: 4px;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--text);
|
||||
color: var(--bg);
|
||||
html.font-plantin {
|
||||
--font: 'Plantin MT Pro', Georgia, serif;
|
||||
--body-weight: 300;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
html.font-alegreya {
|
||||
--font: 'Alegreya', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction {
|
||||
--font: 'Redaction', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-redaction-35 {
|
||||
--font: 'Redaction 35', Georgia, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-sohne {
|
||||
--font: 'SohneBuch', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-space {
|
||||
--font: 'Space Mono', ui-monospace, Menlo, monospace;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-georgia {
|
||||
--font: Georgia, 'Times New Roman', Times, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-palatino {
|
||||
--font: Palatino, 'Palatino Linotype', 'Book Antiqua', serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-iowan {
|
||||
--font: 'Iowan Old Style', 'Palatino Linotype', Palatino, serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-system {
|
||||
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
html.font-courier {
|
||||
--font: 'Courier New', Courier, ui-monospace, monospace;
|
||||
--body-weight: 400;
|
||||
}
|
||||
|
||||
.dark-mode,
|
||||
html.theme-navy,
|
||||
html.theme-dusk,
|
||||
html.theme-midnight,
|
||||
html.theme-indigo,
|
||||
html.theme-storm,
|
||||
html.theme-slate,
|
||||
html.theme-ink,
|
||||
html.theme-pine,
|
||||
html.theme-wine,
|
||||
html.theme-cocoa {
|
||||
--text: #fbfbf7;
|
||||
--muted: #9aa4b8;
|
||||
--accent: #e78c88;
|
||||
--accent-soft: #c45d52;
|
||||
--navy: #3c48a8;
|
||||
}
|
||||
|
||||
html.theme-paper {
|
||||
--bg: #f6f7f9;
|
||||
--bg-secondary: #e9ebef;
|
||||
--text: #2e3140;
|
||||
--muted: #6a6d7a;
|
||||
--accent: #c45d52;
|
||||
--accent-soft: #e78c88;
|
||||
--navy: #2a2c61;
|
||||
--hairline: #d8dbe2;
|
||||
--code-bg: #f3e4e2;
|
||||
--img-matte: #e9ebef;
|
||||
}
|
||||
|
||||
html.theme-navy {
|
||||
--bg: #12192c;
|
||||
--bg-secondary: #1a2238;
|
||||
--hairline: #2a334c;
|
||||
--code-bg: #221e34;
|
||||
--img-matte: #1a2238;
|
||||
}
|
||||
|
||||
html.theme-dusk {
|
||||
--bg: #1a2236;
|
||||
--bg-secondary: #252d44;
|
||||
--hairline: #343e58;
|
||||
--code-bg: #2c2840;
|
||||
--img-matte: #252d44;
|
||||
}
|
||||
|
||||
html.theme-midnight {
|
||||
--bg: #0e1424;
|
||||
--bg-secondary: #161d30;
|
||||
--hairline: #242c42;
|
||||
--code-bg: #1c1a2c;
|
||||
--img-matte: #161d30;
|
||||
}
|
||||
|
||||
html.theme-indigo {
|
||||
--bg: #16142a;
|
||||
--bg-secondary: #1f1c38;
|
||||
--hairline: #2e2a4a;
|
||||
--code-bg: #241e38;
|
||||
--img-matte: #1f1c38;
|
||||
}
|
||||
|
||||
html.theme-storm {
|
||||
--bg: #1c2438;
|
||||
--bg-secondary: #283044;
|
||||
--hairline: #3a4458;
|
||||
--code-bg: #2c2840;
|
||||
--img-matte: #283044;
|
||||
}
|
||||
|
||||
html.theme-slate {
|
||||
--bg: #1e212b;
|
||||
--bg-secondary: #2a2d3a;
|
||||
--hairline: #3a3e4c;
|
||||
--code-bg: #322833;
|
||||
--img-matte: #2a2d3a;
|
||||
}
|
||||
|
||||
html.theme-ink {
|
||||
--bg: #0c0c0c;
|
||||
--bg-secondary: #1c1c1c;
|
||||
--dot-color: #666;
|
||||
--muted: #a3a3a3;
|
||||
|
||||
--color-white-500: #fbfbf7;
|
||||
--hairline: #2a2a2a;
|
||||
--code-bg: #221c20;
|
||||
--img-matte: #1c1c1c;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
html.theme-pine {
|
||||
--bg: #121c22;
|
||||
--bg-secondary: #1a262c;
|
||||
--hairline: #2a3840;
|
||||
--code-bg: #1c2428;
|
||||
--img-matte: #1a262c;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
html.theme-wine {
|
||||
--bg: #1a1520;
|
||||
--bg-secondary: #261c28;
|
||||
--hairline: #3a2c38;
|
||||
--code-bg: #2a1e28;
|
||||
--img-matte: #261c28;
|
||||
}
|
||||
|
||||
.flex-ff {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.col {
|
||||
flex-direction: column;
|
||||
html.theme-cocoa {
|
||||
--bg: #1c1814;
|
||||
--bg-secondary: #2a241c;
|
||||
--hairline: #3c3428;
|
||||
--code-bg: #2c241c;
|
||||
--img-matte: #2a241c;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg);
|
||||
overscroll-behavior-y: none;
|
||||
overscroll-behavior-x: auto;
|
||||
color: var(--text);
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font);
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
line-height: 1.65;
|
||||
font-weight: var(--body-weight);
|
||||
}
|
||||
|
||||
strong,
|
||||
b {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* full-page film grain */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
opacity: 0.06;
|
||||
opacity: 0.05;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
html:not(.theme-paper) body::after,
|
||||
.dark-mode body::after {
|
||||
mix-blend-mode: screen;
|
||||
opacity: 0.045;
|
||||
opacity: 0.04;
|
||||
}
|
||||
|
||||
/* quick page transition */
|
||||
.page-enter-active,
|
||||
.page-leave-active {
|
||||
transition: opacity 0.18s ease;
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.page-enter-from,
|
||||
.page-leave-to {
|
||||
opacity: 0;
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.basic-link {
|
||||
color: var(--text);
|
||||
transition: opacity 0.2s ease;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.basic-link:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.punch-link {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
article {
|
||||
position: relative;
|
||||
color: var(--color-white-500);
|
||||
text-decoration: none;
|
||||
transition: all 200ms ease;
|
||||
padding: 0 4px;
|
||||
margin: 0 -4px;
|
||||
background-image: linear-gradient(
|
||||
var(--color-white-500),
|
||||
var(--color-white-500)
|
||||
);
|
||||
background-size: 100% 1px;
|
||||
background-position: bottom;
|
||||
background-repeat: no-repeat;
|
||||
mix-blend-mode: screen;
|
||||
width: var(--col);
|
||||
margin-left: var(--col-left);
|
||||
margin-right: auto;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.punch-link:hover {
|
||||
background-size: 100% 100%;
|
||||
background-image: linear-gradient(#fff, #fff);
|
||||
color: #000;
|
||||
article h2,
|
||||
article h3 {
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
font-size: 1rem;
|
||||
margin: 2.25rem 0 0.75rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: small;
|
||||
article > h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
article p {
|
||||
margin: 0 0 1em;
|
||||
}
|
||||
|
||||
article ul,
|
||||
article ol {
|
||||
margin: 0 0 1em;
|
||||
padding-left: 1.2em;
|
||||
}
|
||||
|
||||
article li {
|
||||
margin: 0 0 0.35em;
|
||||
}
|
||||
|
||||
article li p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
article hr {
|
||||
margin: 2.5rem 0;
|
||||
border: none;
|
||||
border-top: 1px solid var(--hairline);
|
||||
}
|
||||
|
||||
article blockquote {
|
||||
--hand-w: 2.35rem;
|
||||
--hand-h: calc(var(--hand-w) * 44 / 100);
|
||||
|
||||
position: relative;
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.15rem 0 0.15rem 3.15rem;
|
||||
border: 0;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: 1.08em;
|
||||
line-height: 1.55;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.back-link .arrow {
|
||||
transition: transform 0.2s ease;
|
||||
article blockquote p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.back-link:hover .arrow {
|
||||
transform: translateX(-4px);
|
||||
article blockquote::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0.35em;
|
||||
left: 0;
|
||||
width: var(--hand-w);
|
||||
height: var(--hand-h);
|
||||
background: url('~/assets/images/L.png') center / contain no-repeat;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
html:not(.theme-paper) article blockquote::before,
|
||||
.dark-mode article blockquote::before {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
article img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius);
|
||||
background: var(--img-matte);
|
||||
}
|
||||
|
||||
article p:has(> img) {
|
||||
margin: 1.25rem 0;
|
||||
}
|
||||
|
||||
article .article-body > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.86em;
|
||||
background: var(--code-bg);
|
||||
padding: 0.12em 0.35em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0 0 1em;
|
||||
padding: 0.9rem 1rem;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius);
|
||||
overflow-x: auto;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
pre code {
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
body {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<span class="margin-note"><slot /></span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.margin-note {
|
||||
display: block;
|
||||
margin: 0.4rem 0 1rem;
|
||||
font-size: 0.85rem;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.margin-note {
|
||||
position: absolute;
|
||||
left: calc(100% + 2rem);
|
||||
width: var(--note-width);
|
||||
margin: 0;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
bleed?: boolean | string;
|
||||
}>();
|
||||
|
||||
const isBleed = computed(
|
||||
() =>
|
||||
props.bleed === true ||
|
||||
props.bleed === '' ||
|
||||
props.bleed === 'true',
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="notice" :class="{ 'notice--bleed': isBleed }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.notice {
|
||||
background: var(--navy);
|
||||
color: #f6f7f9;
|
||||
padding: 1rem 1.15rem;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.notice :deep(a) {
|
||||
color: var(--accent-soft);
|
||||
}
|
||||
|
||||
.notice :deep(a:hover) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.notice :deep(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.notice--bleed {
|
||||
box-sizing: border-box;
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
margin: 1.5rem 0 1.5rem calc(-1 * var(--col-left));
|
||||
padding: 1.15rem var(--col-left);
|
||||
padding-right: calc(100vw - var(--col-left) - var(--col));
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
BG_THEMES,
|
||||
BG_THEME_STORAGE_KEY,
|
||||
DEFAULT_BG_THEME,
|
||||
isBgThemeId,
|
||||
type BgThemeId,
|
||||
} from '~/utils/themes';
|
||||
|
||||
export function useBgTheme() {
|
||||
const id = useState<BgThemeId>('bg-theme', () => DEFAULT_BG_THEME);
|
||||
const colorMode = useColorMode();
|
||||
|
||||
const current = computed(
|
||||
() => BG_THEMES.find((theme) => theme.id === id.value) ?? BG_THEMES[0],
|
||||
);
|
||||
|
||||
const applyClass = (next: BgThemeId) => {
|
||||
if (!import.meta.client) return;
|
||||
for (const theme of BG_THEMES) {
|
||||
document.documentElement.classList.remove(`theme-${theme.id}`);
|
||||
}
|
||||
document.documentElement.classList.add(`theme-${next}`);
|
||||
colorMode.preference = next === 'paper' ? 'light' : 'dark';
|
||||
};
|
||||
|
||||
const setTheme = (next: BgThemeId) => {
|
||||
id.value = next;
|
||||
applyClass(next);
|
||||
if (import.meta.client) {
|
||||
localStorage.setItem(BG_THEME_STORAGE_KEY, next);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const saved = localStorage.getItem(BG_THEME_STORAGE_KEY);
|
||||
const next = isBgThemeId(saved) ? saved : id.value;
|
||||
setTheme(next);
|
||||
});
|
||||
|
||||
return { id, current, setTheme, themes: BG_THEMES };
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
BODY_FONTS,
|
||||
BODY_FONT_STORAGE_KEY,
|
||||
DEFAULT_BODY_FONT,
|
||||
isBodyFontId,
|
||||
type BodyFontId,
|
||||
} from '~/utils/fonts';
|
||||
|
||||
export function useBodyFont() {
|
||||
const id = useState<BodyFontId>('body-font', () => DEFAULT_BODY_FONT);
|
||||
|
||||
const applyClass = (next: BodyFontId) => {
|
||||
if (!import.meta.client) return;
|
||||
for (const font of BODY_FONTS) {
|
||||
document.documentElement.classList.remove(`font-${font.id}`);
|
||||
}
|
||||
document.documentElement.classList.add(`font-${next}`);
|
||||
};
|
||||
|
||||
const setFont = (next: BodyFontId) => {
|
||||
id.value = next;
|
||||
applyClass(next);
|
||||
if (import.meta.client) {
|
||||
localStorage.setItem(BODY_FONT_STORAGE_KEY, next);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const saved = localStorage.getItem(BODY_FONT_STORAGE_KEY);
|
||||
const next = isBodyFontId(saved) ? saved : id.value;
|
||||
setFont(next);
|
||||
});
|
||||
|
||||
return { id, setFont, fonts: BODY_FONTS };
|
||||
}
|
||||
+9
-62
@@ -9,68 +9,15 @@ useHead({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<svg width="0" height="0" aria-hidden="true" style="position: absolute">
|
||||
<filter id="grainy" x="0" y="0" width="100%" height="100%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency=".85"
|
||||
numOctaves="3"
|
||||
></feTurbulence>
|
||||
<feColorMatrix type="saturate" values="0"></feColorMatrix>
|
||||
<feBlend mode="multiply" in="SourceGraphic"></feBlend>
|
||||
</filter>
|
||||
</svg>
|
||||
<div class="error-card">
|
||||
<div class="error-content">
|
||||
<h1>{{ error.status }}</h1>
|
||||
<p>
|
||||
{{ error.message ?? 'Sorry, something went wrong.' }}
|
||||
</p>
|
||||
<p>
|
||||
<NuxtLink to="/" class="punch-link"
|
||||
>Back to the homepage.</NuxtLink
|
||||
<NuxtLayout
|
||||
name="main"
|
||||
:title="String(props.error.statusCode ?? 500)"
|
||||
:description="props.error.message ?? 'Sorry, something went wrong.'"
|
||||
>
|
||||
<article>
|
||||
<p>
|
||||
<NuxtLink to="/">Back to the homepage.</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 16rem;
|
||||
color: var(--bg);
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.error-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url('~/assets/images/blend.jpeg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
filter: url(#grainy);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
color: var(--text);
|
||||
}
|
||||
</style>
|
||||
|
||||
+241
-272
@@ -1,332 +1,301 @@
|
||||
<script lang="ts">
|
||||
// module-scoped so it survives client-side navigation —
|
||||
// the intro should only ever play once per visit
|
||||
let introPlayed = false;
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
description?: string;
|
||||
}>();
|
||||
}>(),
|
||||
{
|
||||
title: "Hey, I'm Jaron.",
|
||||
description: 'Software engineer by trade — builder by nature.',
|
||||
},
|
||||
);
|
||||
|
||||
const theme = useColorMode();
|
||||
const { id, themes, setTheme } = useBgTheme();
|
||||
const { id: fontId, fonts, setFont } = useBodyFont();
|
||||
|
||||
const toggleTheme = () => {
|
||||
theme.preference = theme.preference === 'light' ? 'dark' : 'light';
|
||||
const onThemeChange = (event: Event) => {
|
||||
const value = (event.target as HTMLSelectElement).value;
|
||||
const next = themes.find((theme) => theme.id === value);
|
||||
if (next) setTheme(next.id);
|
||||
};
|
||||
|
||||
const { $gsap } = useNuxtApp();
|
||||
const pageEl = useTemplateRef('pageEl');
|
||||
const initialized = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
initialized.value = true;
|
||||
|
||||
if (!$gsap || !pageEl.value || introPlayed) return;
|
||||
introPlayed = true;
|
||||
|
||||
const reduced = window.matchMedia(
|
||||
'(prefers-reduced-motion: reduce)',
|
||||
).matches;
|
||||
|
||||
const ctx = $gsap.context(() => {
|
||||
if (reduced) return;
|
||||
|
||||
const tl = $gsap.timeline({
|
||||
defaults: { ease: 'power3.out' },
|
||||
});
|
||||
|
||||
tl.from('.header-lower', {
|
||||
autoAlpha: 0,
|
||||
y: 12,
|
||||
duration: 0.9,
|
||||
ease: 'power2.out',
|
||||
})
|
||||
.from('.name', { yPercent: 110, duration: 0.9 }, 0.2)
|
||||
.from(
|
||||
'.title',
|
||||
{ yPercent: 120, autoAlpha: 0, duration: 0.8 },
|
||||
0.35,
|
||||
)
|
||||
.from(
|
||||
['.footer-upper', '.footer-lower'],
|
||||
{ autoAlpha: 0, duration: 0.7, ease: 'power2.out' },
|
||||
0.55,
|
||||
)
|
||||
.from('.header-upper', { autoAlpha: 0, duration: 0.7 }, 0.55);
|
||||
|
||||
tl.from(
|
||||
'main',
|
||||
{ autoAlpha: 0, y: 16, duration: 0.8, ease: 'power2.out' },
|
||||
0.4,
|
||||
);
|
||||
}, pageEl.value);
|
||||
|
||||
onBeforeUnmount(() => ctx.revert());
|
||||
});
|
||||
const onFontChange = (event: Event) => {
|
||||
const value = (event.target as HTMLSelectElement).value;
|
||||
const next = fonts.find((font) => font.id === value);
|
||||
if (next) setFont(next.id);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-show="initialized">
|
||||
<svg width="0" height="0" aria-hidden="true" style="position: absolute">
|
||||
<filter id="grainy" x="0" y="0" width="100%" height="100%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency=".85"
|
||||
numOctaves="3"
|
||||
></feTurbulence>
|
||||
<feColorMatrix type="saturate" values="0"></feColorMatrix>
|
||||
<feBlend mode="multiply" in="SourceGraphic"></feBlend>
|
||||
</filter>
|
||||
</svg>
|
||||
<div class="page-wrapper" ref="pageEl">
|
||||
<header class="header">
|
||||
<div class="header-upper">
|
||||
<slot name="header-upper">
|
||||
<div class="flex-center header-upper-nav">
|
||||
<button
|
||||
class="theme-toggle"
|
||||
@click="toggleTheme"
|
||||
v-if="!theme.unknown"
|
||||
>
|
||||
{{ theme.value === 'dark' ? '🌚' : '🌝' }}
|
||||
</button>
|
||||
<div class="flex-ff"></div>
|
||||
<a
|
||||
class="basic-link"
|
||||
href="https://github.com/jaronpate"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
<NuxtLink to="/writing" class="basic-link">
|
||||
Writing
|
||||
<div class="site">
|
||||
<header class="hero">
|
||||
<div class="hero__inner">
|
||||
<div class="hero__nav">
|
||||
<NuxtLink to="/" class="wordmark" aria-label="Home">
|
||||
@jaronpate
|
||||
</NuxtLink>
|
||||
<nav class="site-nav">
|
||||
<NuxtLink to="/writing">Writing</NuxtLink>
|
||||
<a href="https://github.com/jaronpate">GitHub</a>
|
||||
<label class="theme-picker">
|
||||
<span class="visually-hidden">Background</span>
|
||||
<select
|
||||
class="theme-select"
|
||||
:value="id"
|
||||
@change="onThemeChange"
|
||||
>
|
||||
<option
|
||||
v-for="theme in themes"
|
||||
:key="theme.id"
|
||||
:value="theme.id"
|
||||
>
|
||||
{{ theme.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="theme-picker">
|
||||
<span class="visually-hidden">Font</span>
|
||||
<select
|
||||
class="theme-select"
|
||||
:value="fontId"
|
||||
@change="onFontChange"
|
||||
>
|
||||
<option
|
||||
v-for="font in fonts"
|
||||
:key="font.id"
|
||||
:value="font.id"
|
||||
>
|
||||
{{ font.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="header-lower">
|
||||
<slot name="header-lower">
|
||||
<div class="mask">
|
||||
<h1 class="name">
|
||||
{{ title || "Hey, I'm Jaron" }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="mask">
|
||||
<p class="title">
|
||||
{{
|
||||
description ||
|
||||
'Software Engineer by trade — builder by nature.'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</slot>
|
||||
<h1 class="hero__title">{{ title }}</h1>
|
||||
<p v-if="description" class="hero__sub">{{ description }}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="page-body">
|
||||
<slot />
|
||||
<div style="flex: 1"></div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-upper flex-center">
|
||||
<p>
|
||||
<a class="punch-link" href="mailto:yo@jp.wtf"
|
||||
>Say hi.</a
|
||||
>
|
||||
</p>
|
||||
<footer class="site-footer">
|
||||
<a href="mailto:yo@jp.wtf">yo@jp.wtf</a>
|
||||
<div class="flex-ff"></div>
|
||||
<p style="text-align: right">Let's make something cool.</p>
|
||||
</div>
|
||||
<div class="footer-lower flex-center" style="gap: var(--gap)">
|
||||
<p style="text-align: right">Est. 2003</p>
|
||||
<!-- <p style="font-family: monospace">🐟🎩.dev</p> -->
|
||||
<div class="flex-ff"></div>
|
||||
<p style="text-align: right">Austin, TX</p>
|
||||
</div>
|
||||
<span>Est. 2003</span>
|
||||
<span>Austin, TX</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-wrapper {
|
||||
--main-width: 50vw;
|
||||
width: var(--main-width);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/*gap: 1.5rem;*/
|
||||
.site {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
.page-body {
|
||||
flex: 1;
|
||||
padding-top: 2.25rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
--hero-text: #fbfbf7;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
color: var(--hero-text);
|
||||
padding: 1.25rem 0 3rem;
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background:
|
||||
linear-gradient(rgba(12, 12, 18, 0.1), rgba(12, 12, 18, 0.18)),
|
||||
url('~/assets/images/blend.jpeg') center / cover no-repeat;
|
||||
}
|
||||
|
||||
.hero__inner {
|
||||
width: var(--col);
|
||||
margin-left: var(--col-left);
|
||||
}
|
||||
|
||||
.hero__nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 2.75rem;
|
||||
font-family: var(--font-plain);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
svg[height='0'] {
|
||||
position: fixed;
|
||||
.wordmark,
|
||||
.site-nav a,
|
||||
.theme-select {
|
||||
color: var(--hero-text);
|
||||
}
|
||||
|
||||
.mask {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 3rem;
|
||||
.wordmark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
text-decoration: none;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 1.15rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
color: var(--color-white-500);
|
||||
.wordmark:hover,
|
||||
.wordmark.router-link-active {
|
||||
color: var(--hero-text);
|
||||
text-decoration: none;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.header {
|
||||
.site-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.header-upper {
|
||||
height: 8vh;
|
||||
min-height: 3.5rem;
|
||||
}
|
||||
|
||||
.header-upper-nav {
|
||||
padding: 0 2rem;
|
||||
height: 100%;
|
||||
font-size: small;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.header-lower {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 3rem 2.5rem;
|
||||
color: var(--color-white-500);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
.site-nav a:hover,
|
||||
.site-nav a.router-link-active {
|
||||
color: var(--hero-text);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.header-lower::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url('~/assets/images/blend.jpeg');
|
||||
background-size: cover;
|
||||
background-position: left top;
|
||||
.theme-picker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.theme-select {
|
||||
appearance: none;
|
||||
background-color: rgba(255, 255, 255, 0.14);
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath fill='%23fbfbf7' d='M0 0l5 6 5-6z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
filter: url(#grainy);
|
||||
border-radius: 16px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.footer-upper {
|
||||
padding: 3.25rem 2.5rem;
|
||||
color: var(--color-white-500);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.footer-upper::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url('~/assets/images/blend.jpeg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
filter: url(#grainy);
|
||||
border-radius: 16px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.footer-lower {
|
||||
padding: 0 2rem;
|
||||
height: 8vh;
|
||||
min-height: 3.5rem;
|
||||
font-size: small;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
background: transparent;
|
||||
border: none;
|
||||
background-position: right 0.55em center;
|
||||
background-size: 0.5em;
|
||||
border: 1px solid rgba(251, 251, 247, 0.35);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
color: var(--text);
|
||||
transition: opacity 0.2s ease;
|
||||
font-family: var(--font-plain);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1;
|
||||
padding: 0.35em 1.55em 0.35em 0.65em;
|
||||
}
|
||||
|
||||
.theme-select:hover {
|
||||
background-color: rgba(255, 255, 255, 0.22);
|
||||
}
|
||||
|
||||
.theme-select option {
|
||||
color: #1a1a1a;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
.hero__title {
|
||||
margin: 0;
|
||||
font-size: 2.75rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.page-wrapper {
|
||||
--main-width: 80vw;
|
||||
}
|
||||
.hero__sub {
|
||||
margin: 0.6rem 0 0;
|
||||
font-weight: var(--body-weight);
|
||||
font-style: italic;
|
||||
font-size: 1.05rem;
|
||||
color: rgba(251, 251, 247, 0.88);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-upper-nav {
|
||||
padding: 0 1.5rem;
|
||||
.site-footer {
|
||||
--flare-length: 1.125rem;
|
||||
--flare-spread: 1.125rem;
|
||||
--flare-overhang: 1.5rem;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1rem;
|
||||
width: var(--col);
|
||||
margin-top: 2rem;
|
||||
margin-left: var(--col-left);
|
||||
margin-right: auto;
|
||||
padding: calc(var(--flare-spread) + 0.45rem) 0 0.85rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.name {
|
||||
.site-footer::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(-1 * var(--flare-overhang));
|
||||
right: calc(-1 * var(--flare-overhang));
|
||||
height: var(--flare-spread);
|
||||
background-color: var(--muted);
|
||||
pointer-events: none;
|
||||
-webkit-mask:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M20 8H10M10 8C5 8 2.5 4.5 1 2M10 8C5 8 2.5 11.5 1 14' stroke='black' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E")
|
||||
left center / var(--flare-length) 100% no-repeat,
|
||||
linear-gradient(#000, #000) center /
|
||||
calc(100% - var(--flare-length) * 1.55) 1px no-repeat,
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M0 8H10M10 8C15 8 17.5 4.5 19 2M10 8C15 8 17.5 11.5 19 14' stroke='black' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E")
|
||||
right center / var(--flare-length) 100% no-repeat;
|
||||
mask:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M20 8H10M10 8C5 8 2.5 4.5 1 2M10 8C5 8 2.5 11.5 1 14' stroke='black' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E")
|
||||
left center / var(--flare-length) 100% no-repeat,
|
||||
linear-gradient(#000, #000) center /
|
||||
calc(100% - var(--flare-length) * 1.55) 1px no-repeat,
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M0 8H10M10 8C15 8 17.5 4.5 19 2M10 8C15 8 17.5 11.5 19 14' stroke='black' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E")
|
||||
right center / var(--flare-length) 100% no-repeat;
|
||||
}
|
||||
|
||||
.site-footer a {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.site-footer a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.flex-ff {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hero {
|
||||
padding-bottom: 2.25rem;
|
||||
}
|
||||
|
||||
.hero__nav {
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
--main-width: 100vw;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.header-lower,
|
||||
.footer-upper {
|
||||
padding: 1.5rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.footer-lower {
|
||||
padding: 1.5rem 1.5rem;
|
||||
}
|
||||
|
||||
.header-lower::before,
|
||||
.footer-upper::before {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+46
-417
@@ -1,10 +1,4 @@
|
||||
<script lang="ts">
|
||||
// module-scoped so the reveal only plays once per visit
|
||||
let sectionsRevealed = false;
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
DEFAULT_DESCRIPTION,
|
||||
DEFAULT_IMAGE,
|
||||
@@ -36,14 +30,6 @@ useHead({
|
||||
link: [{ rel: 'canonical', href: SITE_URL }],
|
||||
});
|
||||
|
||||
const titles = [
|
||||
"Hey, I'm Jaron.",
|
||||
'Nice to meet you.',
|
||||
'Welcome.',
|
||||
'Willkommen.',
|
||||
'Bienvenue.',
|
||||
];
|
||||
|
||||
const selectedWorks = [
|
||||
{
|
||||
title: 'Edlink',
|
||||
@@ -186,118 +172,21 @@ useHead({
|
||||
],
|
||||
});
|
||||
|
||||
const photos = [
|
||||
{
|
||||
src: '/photography/athens_red_scooter.jpg',
|
||||
alt: 'Red scooter in Athens',
|
||||
location: 'Athens',
|
||||
year: '2026',
|
||||
},
|
||||
{
|
||||
src: '/photography/athens_subway.jpg',
|
||||
alt: 'Athens metro station',
|
||||
location: 'Athens',
|
||||
year: '2026',
|
||||
},
|
||||
{
|
||||
src: '/photography/athens_sunset.jpg',
|
||||
alt: 'Athens at sunset',
|
||||
location: 'Athens',
|
||||
year: '2026',
|
||||
},
|
||||
{
|
||||
src: '/photography/london_porsche.jpg',
|
||||
alt: 'Porsche shop in London',
|
||||
location: 'London',
|
||||
year: '2025',
|
||||
},
|
||||
{
|
||||
src: '/photography/underground.jpg',
|
||||
alt: 'Entrace to the London underground',
|
||||
location: 'London',
|
||||
year: '2026',
|
||||
},
|
||||
{
|
||||
src: '/photography/austin_parking.jpg',
|
||||
alt: 'Parking garage in Austin',
|
||||
location: 'Austin',
|
||||
year: '2024',
|
||||
},
|
||||
];
|
||||
|
||||
const pageTitle = ref(titles[0]);
|
||||
|
||||
const rotateTitle = () => {
|
||||
setTimeout(() => {
|
||||
const i = titles.findIndex((t) => t === pageTitle.value);
|
||||
pageTitle.value = titles[i + 1] ?? titles[0] ?? 'Oops!';
|
||||
rotateTitle();
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
});
|
||||
|
||||
const { $gsap } = useNuxtApp();
|
||||
const pageRoot = ref<HTMLElement | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
rotateTitle();
|
||||
|
||||
useHead({
|
||||
title: pageTitle,
|
||||
});
|
||||
|
||||
if (!$gsap || !pageRoot.value || sectionsRevealed) return;
|
||||
sectionsRevealed = true;
|
||||
|
||||
const reduced = window.matchMedia(
|
||||
'(prefers-reduced-motion: reduce)',
|
||||
).matches;
|
||||
|
||||
const ctx = $gsap.context(() => {
|
||||
if (reduced) return;
|
||||
|
||||
// sections in view on load are revealed by the layout's intro;
|
||||
// below-the-fold sections rise in as they enter the viewport
|
||||
$gsap.utils.toArray<HTMLElement>('main section').forEach((el) => {
|
||||
const inView = el.getBoundingClientRect().top < window.innerHeight;
|
||||
if (inView) return;
|
||||
|
||||
$gsap.from(el, {
|
||||
autoAlpha: 0,
|
||||
y: 24,
|
||||
duration: 0.6,
|
||||
ease: 'power2.out',
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
start: 'top 92%',
|
||||
},
|
||||
});
|
||||
});
|
||||
}, pageRoot.value);
|
||||
|
||||
onBeforeUnmount(() => ctx.revert());
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="pageRoot">
|
||||
<NuxtLayout name="main">
|
||||
<section class="rows">
|
||||
<h5>Selected Work</h5>
|
||||
<div class="section__content">
|
||||
<a
|
||||
v-for="work in selectedWorks"
|
||||
:key="work.title"
|
||||
class="work"
|
||||
:href="work.link"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 class="work__title">{{ work.title }}</h3>
|
||||
<p class="work__description">
|
||||
{{ work.description }}
|
||||
<article>
|
||||
<h3>Selected Work</h3>
|
||||
<template v-for="work in selectedWorks" :key="work.title">
|
||||
<p>
|
||||
<a :href="work.link" target="_blank" rel="noopener">{{
|
||||
work.title
|
||||
}}</a>
|
||||
— {{ work.description }}
|
||||
</p>
|
||||
<div class="work__images">
|
||||
<figure
|
||||
@@ -308,34 +197,26 @@ onMounted(() => {
|
||||
<img :src="image.src" :alt="image.alt" />
|
||||
</figure>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="rows">
|
||||
<h5>Projects</h5>
|
||||
<div class="section__content">
|
||||
<a
|
||||
v-for="project in projects"
|
||||
:key="project.href"
|
||||
:href="project.href"
|
||||
target="_blank"
|
||||
class="project"
|
||||
>
|
||||
<h3 class="project__title">{{ project.title }}</h3>
|
||||
<p class="project__description">
|
||||
{{ project.description }}
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h5>About</h5>
|
||||
<div class="section__content">
|
||||
</template>
|
||||
|
||||
<h3>Projects</h3>
|
||||
<ul>
|
||||
<li v-for="project in projects" :key="project.href">
|
||||
<p>
|
||||
I left school, my job, and moved to Austin in 2022 to write
|
||||
code full time. That was definitely one of the best and
|
||||
most life changing decisions I've ever made. I've learned
|
||||
a lot since then and <em>still</em> have much more to learn.
|
||||
<a :href="project.href" target="_blank" rel="noopener">{{
|
||||
project.title
|
||||
}}</a>
|
||||
— {{ project.description }}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>About</h3>
|
||||
<p>
|
||||
I left school, my job, and moved to Austin in 2022 to write code
|
||||
full time. That was definitely one of the best and most life
|
||||
changing decisions I've ever made. I've learned a lot since then
|
||||
and <em>still</em> have much more to learn.
|
||||
</p>
|
||||
<p>
|
||||
These days I build things because I find joy in making something
|
||||
@@ -343,155 +224,64 @@ onMounted(() => {
|
||||
level of polish that users don't even notice.
|
||||
</p>
|
||||
<p>
|
||||
In a time where code is cheap I pride myself in taste and execution.
|
||||
The best sticking point — in my limited opinion so far — is the
|
||||
way using your product <em>feels</em>. So I want everything I make to
|
||||
simply <em>feel</em> the best to use.
|
||||
In a time where code is cheap I pride myself in taste and
|
||||
execution. The best sticking point — in my limited opinion so far
|
||||
— is the way using your product <em>feels</em>. So I want
|
||||
everything I make to simply <em>feel</em> the best to use.
|
||||
</p>
|
||||
<p>
|
||||
If you want to build something cool or connect
|
||||
<a class="basic-link" href="mailto:yo@jp.wtf">hit me up</a>
|
||||
— we'll grab a coffee or beer if you're in Austin! I
|
||||
still dont know enough people to form a band yet ;)
|
||||
<a href="mailto:yo@jp.wtf">hit me up</a> — we'll grab a coffee or
|
||||
beer if you're in Austin! I still dont know enough people to form
|
||||
a band yet ;)
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<!-- <section class="photos">
|
||||
<h5>Photos</h5>
|
||||
<div class="section__content">
|
||||
<div class="photo-grid">
|
||||
<div
|
||||
v-for="photo in photos"
|
||||
:key="photo.src"
|
||||
class="photo-grid__item"
|
||||
>
|
||||
<div class="photo__image">
|
||||
<img :src="photo.src" :alt="photo.alt" />
|
||||
</div>
|
||||
<div class="photo__overlay">
|
||||
<p>{{ photo.location }}</p>
|
||||
<p>{{ photo.year }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section> -->
|
||||
</article>
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
section {
|
||||
margin-top: calc(var(--gap) * 2);
|
||||
}
|
||||
|
||||
section > .section__content {
|
||||
padding: 0 calc(var(--gap) * 2);
|
||||
}
|
||||
|
||||
/* section:not(:has(h5)) > .section__content {
|
||||
padding: 2rem;
|
||||
} */
|
||||
|
||||
section > h5 {
|
||||
--flare-length: 1.125rem; /* how far the flare extends horizontally */
|
||||
--flare-spread: 1.125rem; /* how far the arms open up/down */
|
||||
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 0 0 var(--gap) 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
section > h5::before {
|
||||
content: '';
|
||||
width: var(--flare-length);
|
||||
height: var(--flare-spread);
|
||||
flex-shrink: 0;
|
||||
background-color: var(--muted);
|
||||
/* Optically center */
|
||||
transform: translateY(-0.12em);
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M20 8H10M10 8C5 8 2.5 4.5 1 2M10 8C5 8 2.5 11.5 1 14' stroke='black' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E")
|
||||
center / 100% 100% no-repeat;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M20 8H10M10 8C5 8 2.5 4.5 1 2M10 8C5 8 2.5 11.5 1 14' stroke='black' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E")
|
||||
center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
section > h5::after {
|
||||
content: '';
|
||||
height: var(--flare-spread);
|
||||
flex-grow: 1;
|
||||
background-color: var(--muted);
|
||||
/* Optically center */
|
||||
transform: translateY(-0.12em);
|
||||
-webkit-mask:
|
||||
linear-gradient(#000, #000) left center /
|
||||
calc(100% - var(--flare-length) * 0.45) 1px no-repeat,
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M0 8H10M10 8C15 8 17.5 4.5 19 2M10 8C15 8 17.5 11.5 19 14' stroke='black' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E")
|
||||
right center / var(--flare-length) 100% no-repeat;
|
||||
mask:
|
||||
linear-gradient(#000, #000) left center /
|
||||
calc(100% - var(--flare-length) * 0.45) 1px no-repeat,
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 16' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M0 8H10M10 8C15 8 17.5 4.5 19 2M10 8C15 8 17.5 11.5 19 14' stroke='black' stroke-width='1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E")
|
||||
right center / var(--flare-length) 100% no-repeat;
|
||||
}
|
||||
|
||||
section:last-of-type {
|
||||
margin-bottom: calc(var(--gap) * 2);
|
||||
}
|
||||
|
||||
.work {
|
||||
}
|
||||
|
||||
.work__images {
|
||||
/*display: flex;
|
||||
flex-wrap: no-wrap;
|
||||
align-items: center;
|
||||
justify-content: center;*/
|
||||
--dot-color: #666;
|
||||
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: max-content;
|
||||
gap: var(--gap);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
margin-top: var(--gap);
|
||||
margin: 1.25rem 0 1.75rem;
|
||||
padding-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.work__image {
|
||||
--dot-size: 6%;
|
||||
|
||||
margin: 0;
|
||||
/*padding: calc(var(--gap) * 2) var(--gap) var(--gap);*/
|
||||
padding: var(--gap);
|
||||
flex-shrink: 0;
|
||||
border-radius: var(--radius);
|
||||
background-color: var(--bg-secondary);
|
||||
background-image: radial-gradient(
|
||||
circle at 4px 4px,
|
||||
var(--dot-color) var(--dot-size),
|
||||
transparent calc(var(--dot-size) + 1%)
|
||||
);
|
||||
|
||||
background-position: 4px 4px;
|
||||
background-size: 12px 12px;
|
||||
background-repeat: repeat;
|
||||
background-color: var(--bg-secondary);
|
||||
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
.work__image img {
|
||||
border-radius: var(--radius);
|
||||
object-fit: cover;
|
||||
max-width: none;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-height: 350px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius);
|
||||
background: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
@@ -499,165 +289,4 @@ section:last-of-type {
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.work__image figcaption {
|
||||
font-size: 0.6rem;
|
||||
line-height: 1;
|
||||
font-family: var(--font-plain);
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 2px;
|
||||
padding: 3px 4px;
|
||||
background: var(--dot-color);
|
||||
/*border: 1px solid var(--dot-color);*/
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rows > .section__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
.project,
|
||||
.work {
|
||||
/*border-bottom: 1px solid var(--muted);*/
|
||||
/*padding: var(--gap) 0;*/
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.work {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.project:first-of-type,
|
||||
.work:first-of-type {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.project:last-of-type,
|
||||
.work:last-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.project:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.project:hover > .project__title {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
font-style: italic;
|
||||
font-family: var(--font-hover);
|
||||
}
|
||||
|
||||
.project__title,
|
||||
.work__title {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
margin-bottom: calc(var(--gap) * 0.5);
|
||||
}
|
||||
|
||||
.project__title::after,
|
||||
.work__title::after {
|
||||
content: '↗';
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 0.4em;
|
||||
font-size: 0.75em;
|
||||
opacity: 0;
|
||||
vertical-align: center;
|
||||
transform: translate(-6px, 6px);
|
||||
transition:
|
||||
opacity 0.25s ease,
|
||||
transform 0.25s ease;
|
||||
}
|
||||
|
||||
.project:hover .project__title::after,
|
||||
.work:hover .work__title::after {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
.project__description,
|
||||
.work__description {
|
||||
margin: 0;
|
||||
font-size: small;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.photo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.photo-grid__item {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.photo-grid__item:hover > .photo__overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.photo__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.photo__image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.photo__overlay {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(2px);
|
||||
color: var(--color-white-500);
|
||||
z-index: 2;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.photo__overlay p {
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.photo__overlay p:nth-child(2) {
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.about p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
section > .section__content {
|
||||
padding: 0 var(--gap);
|
||||
}
|
||||
|
||||
.photo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,10 +12,14 @@ const articleUrl = `${SITE_URL}${route.path}`;
|
||||
const webpageId = `${articleUrl}#webpage`;
|
||||
const articleId = `${articleUrl}#article`;
|
||||
const blogId = `${SITE_URL}/writing#blog`;
|
||||
const contentPath =
|
||||
'/' +
|
||||
(Array.isArray(route.params.slug)
|
||||
? route.params.slug.join('/')
|
||||
: String(route.params.slug ?? ''));
|
||||
|
||||
const { data: page } = await useAsyncData(route.path, () => {
|
||||
return queryCollection('writing')
|
||||
.path(route.path.replace('/writing/', ''))
|
||||
.first();
|
||||
return queryCollection('writing').path(contentPath).first();
|
||||
});
|
||||
|
||||
if (!page.value) {
|
||||
@@ -134,52 +138,33 @@ useHead({
|
||||
],
|
||||
});
|
||||
|
||||
const formatDate = (date: Date | string) => {
|
||||
return new Date(date).toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
};
|
||||
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLayout
|
||||
name="main"
|
||||
:title="page?.title"
|
||||
:description="page?.description"
|
||||
:title="page.title"
|
||||
:description="formatDate(page.date)"
|
||||
>
|
||||
<template #header-upper>
|
||||
<div class="header-upper-inner">
|
||||
<NuxtLink
|
||||
to="/writing"
|
||||
class="back-link"
|
||||
aria-label="Back to writing"
|
||||
>
|
||||
<span class="arrow">←</span>
|
||||
<span>Back</span>
|
||||
</NuxtLink>
|
||||
<article v-if="page">
|
||||
<div class="article-body">
|
||||
<p v-if="page.description">
|
||||
<strong>{{ page.description }}</strong>
|
||||
</p>
|
||||
<ContentRenderer :value="page" />
|
||||
</div>
|
||||
</template>
|
||||
<ContentRenderer
|
||||
class="writing__content"
|
||||
v-if="page"
|
||||
:value="page"
|
||||
/>
|
||||
</article>
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.header-upper-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.writing__content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.writing__content :deep(> :first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
+49
-88
@@ -7,11 +7,9 @@ import {
|
||||
websiteId,
|
||||
} from '~/utils/site';
|
||||
|
||||
// const { data: pages } = await useAsyncData('writing-pages', () =>
|
||||
// queryCollection('writing').order('date', 'DESC').all(),
|
||||
// );
|
||||
|
||||
const pages = ref<any[]>([]);
|
||||
const { data: pages } = await useAsyncData('writing-pages', () =>
|
||||
queryCollection('writing').order('date', 'DESC').all(),
|
||||
);
|
||||
|
||||
const writingUrl = `${SITE_URL}/writing`;
|
||||
const collectionId = `${writingUrl}#collection`;
|
||||
@@ -74,7 +72,7 @@ useHead({
|
||||
name: SITE_NAME,
|
||||
},
|
||||
blogPost: (pages.value ?? []).map((page) => ({
|
||||
'@id': `${writingUrl}/${page.path}#article`,
|
||||
'@id': `${writingUrl}${page.path}#article`,
|
||||
})),
|
||||
},
|
||||
{
|
||||
@@ -85,7 +83,7 @@ useHead({
|
||||
(page, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
url: `${writingUrl}/${page.path}`,
|
||||
url: `${writingUrl}${page.path}`,
|
||||
name: page.title,
|
||||
}),
|
||||
),
|
||||
@@ -114,10 +112,13 @@ useHead({
|
||||
],
|
||||
});
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
// ex. May 5, 2026
|
||||
return `${date.toLocaleDateString('en-US', { month: 'short' })} ${date.getDate()}, ${date.getFullYear()}`;
|
||||
const formatDate = (dateString: string | Date) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
};
|
||||
|
||||
definePageMeta({
|
||||
@@ -126,116 +127,76 @@ definePageMeta({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLayout
|
||||
name="main"
|
||||
title="Writing"
|
||||
description="Thoughts I've had here and there. Maybe you'll find something helpful or interesting here."
|
||||
>
|
||||
<template #header-upper>
|
||||
<div class="header-upper-inner">
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="back-link"
|
||||
aria-label="Back to Home"
|
||||
>
|
||||
<span class="arrow">←</span>
|
||||
<span>Home</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
<section>
|
||||
<div class="section__content">
|
||||
<article>
|
||||
<template v-if="pages?.length">
|
||||
<NuxtLink
|
||||
v-for="page in pages"
|
||||
:to="'/writing/' + page.path"
|
||||
:key="page.path"
|
||||
class="article"
|
||||
:to="'/writing' + page.path"
|
||||
class="listing"
|
||||
>
|
||||
<h3 class="article__title">{{ page.title }}</h3>
|
||||
<!-- <p class="article__date">
|
||||
{{ formatDate(page.date) }}
|
||||
</p> -->
|
||||
<p class="article__description">
|
||||
{{ page.description }}
|
||||
</p>
|
||||
<span class="listing__title">{{ page.title }}</span>
|
||||
<time :datetime="new Date(page.date).toISOString()">{{
|
||||
formatDate(page.date)
|
||||
}}</time>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="empty-state">
|
||||
<p>No thoughts...head empty</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
<p v-else class="empty-state">No thoughts...head empty</p>
|
||||
</article>
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.header-upper-inner {
|
||||
.listing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
section > .section__content {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
section:not(:has(h5)) > .section__content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.article {
|
||||
border-bottom: 1px solid var(--muted);
|
||||
padding: 1rem 0;
|
||||
display: block;
|
||||
align-items: baseline;
|
||||
gap: 1rem;
|
||||
margin: 0 0 0.65rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.article:first-of-type {
|
||||
padding-top: 0;
|
||||
.listing:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.article:last-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.article:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.article:hover > .article__title {
|
||||
.listing:hover .listing__title {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
text-underline-offset: 3px;
|
||||
font-style: italic;
|
||||
font-family: var(--font-hover);
|
||||
}
|
||||
|
||||
.article__title {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
.listing__title {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.article__date {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
font-size: small;
|
||||
.listing time {
|
||||
margin-left: auto;
|
||||
font-family: var(--font-plain);
|
||||
font-size: 0.75rem;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.article__description {
|
||||
margin: 0;
|
||||
font-size: small;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.listing {
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.listing time {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
return {
|
||||
provide: {
|
||||
gsap,
|
||||
ScrollTrigger,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
export const BODY_FONTS = [
|
||||
{ id: 'plantin', label: 'Plantin' },
|
||||
{ id: 'alegreya', label: 'Alegreya' },
|
||||
{ id: 'redaction', label: 'Redaction' },
|
||||
{ id: 'redaction-35', label: 'Redaction 35' },
|
||||
{ id: 'sohne', label: 'Sohne' },
|
||||
{ id: 'space', label: 'Space Mono' },
|
||||
{ id: 'georgia', label: 'Georgia' },
|
||||
{ id: 'palatino', label: 'Palatino' },
|
||||
{ id: 'iowan', label: 'Iowan' },
|
||||
{ id: 'system', label: 'System Sans' },
|
||||
{ id: 'courier', label: 'Courier' },
|
||||
] as const;
|
||||
|
||||
export type BodyFontId = (typeof BODY_FONTS)[number]['id'];
|
||||
|
||||
export const DEFAULT_BODY_FONT: BodyFontId = 'plantin';
|
||||
export const BODY_FONT_STORAGE_KEY = 'body-font';
|
||||
|
||||
export function isBodyFontId(value: string | null): value is BodyFontId {
|
||||
return BODY_FONTS.some((font) => font.id === value);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
export const BG_THEMES = [
|
||||
{ id: 'dusk', label: 'Dusk' },
|
||||
{ id: 'navy', label: 'Navy' },
|
||||
{ id: 'midnight', label: 'Midnight' },
|
||||
{ id: 'indigo', label: 'Indigo' },
|
||||
{ id: 'storm', label: 'Storm' },
|
||||
{ id: 'slate', label: 'Slate' },
|
||||
{ id: 'ink', label: 'Ink' },
|
||||
{ id: 'pine', label: 'Pine' },
|
||||
{ id: 'wine', label: 'Wine' },
|
||||
{ id: 'cocoa', label: 'Cocoa' },
|
||||
{ id: 'paper', label: 'Paper' },
|
||||
] as const;
|
||||
|
||||
export type BgThemeId = (typeof BG_THEMES)[number]['id'];
|
||||
|
||||
export const DEFAULT_BG_THEME: BgThemeId = 'dusk';
|
||||
export const BG_THEME_STORAGE_KEY = 'bg-theme';
|
||||
|
||||
export function isBgThemeId(value: string | null): value is BgThemeId {
|
||||
return BG_THEMES.some((theme) => theme.id === value);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Notes in the margin
|
||||
date: 2026-08-22
|
||||
description: A quick look at the two asides this site knows how to make.
|
||||
---
|
||||
|
||||
Most of what I write should just be the thing itself. Sometimes a thought doesn't belong in the sentence, though, it belongs next to it. :margin-note[(like this — a margin note sits beside the column on a wide screen, and drops under the paragraph on a small one)] That's what margin notes are for. Parentheticals you can actually skip.
|
||||
|
||||
Notices are the opposite. They interrupt on purpose.
|
||||
|
||||
::notice
|
||||
**A note you shouldn't skip.** Use a notice when the aside *is* the point for a minute. It stays in the column, navy, hard to miss.
|
||||
::
|
||||
|
||||
And if it needs to break the column entirely:
|
||||
|
||||
::notice{bleed}
|
||||
**Full-bleed.** Same idea, but it runs edge to edge. Save it for the one time in a piece you actually want to shout.
|
||||
::
|
||||
|
||||
That's the whole vocabulary. Margin for a mutter. Band for a point. Everything else is just the writing.
|
||||
|
||||
> A blockquote gets the little pointing hand in the left margin. Use it when you're quoting someone, or when you want a line to feel pointed at.
|
||||
+11
-6
@@ -29,8 +29,9 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
|
||||
css: ['~/assets/css/main.css'],
|
||||
|
||||
app: {
|
||||
pageTransition: { name: 'page', mode: 'out-in' },
|
||||
head: {
|
||||
htmlAttrs: { lang: 'en' },
|
||||
charset: 'utf-8',
|
||||
@@ -44,17 +45,21 @@ export default defineNuxtConfig({
|
||||
},
|
||||
],
|
||||
link: [{ rel: 'icon', type: 'image/png', href: '/favicon.png' }],
|
||||
script: [
|
||||
{
|
||||
innerHTML: `(function(){try{var t=localStorage.getItem('bg-theme')||'dusk';var f=localStorage.getItem('body-font')||'plantin';document.documentElement.classList.add('theme-'+t,'font-'+f);}catch(e){document.documentElement.classList.add('theme-dusk','font-plantin');}})();`,
|
||||
tagPosition: 'head',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
modules: ['@nuxt/content', '@nuxtjs/color-mode'],
|
||||
colorMode: {
|
||||
preference: 'system', // default value of $colorMode.preference
|
||||
fallback: 'light', // fallback value if not system preference found
|
||||
globalName: '__NUXT_COLOR_MODE__',
|
||||
componentName: 'ColorScheme',
|
||||
preference: 'dark',
|
||||
fallback: 'dark',
|
||||
classPrefix: '',
|
||||
classSuffix: '-mode', // Results in .dark-mode or .light-mode
|
||||
classSuffix: '-mode',
|
||||
storageKey: 'color-mode',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user