Compare commits

..

17 Commits

43 changed files with 2938 additions and 6417 deletions

6
.gitignore vendored
View File

@ -3,3 +3,9 @@ node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
**/_test.*

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
engine-strict=true
resolution-mode=highest

View File

@ -1,6 +1,6 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
@ -8,14 +8,12 @@ If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte@next
npm create svelte@latest
# create a new project in my-app
npm init svelte@next my-app
npm create svelte@latest my-app
```
> Note: the `@next` is temporary
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
@ -29,10 +27,12 @@ npm run dev -- --open
## Building
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
To create a production version of your app:
```bash
npm run build
```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View File

@ -1,11 +0,0 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"$lib": ["src/lib"],
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}

7834
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,23 @@
{
"name": "blog",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview"
},
"devDependencies": {
"@sveltejs/adapter-static": "^1.0.0-next.21",
"@sveltejs/kit": "next",
"mdsvex": "^0.9.8",
"node-sass": "^6.0.1",
"svelte": "^3.42.6",
"svelte-preprocess": "^4.9.8"
},
"type": "module"
"name": "blog.jfmonty2.com",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4",
"hast-util-to-text": "^4.0.0",
"mdast-util-to-string": "^4.0.0",
"mdsvex": "^0.11.0",
"svelte": "^4.0.5",
"unist-util-find": "^3.0.0",
"unist-util-visit": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module"
}

View File

@ -4,12 +4,12 @@
<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="icon" href="/favicon.png" />
<link rel="stylesheet" href="/style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%svelte.head%
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -34,9 +34,8 @@
}
</style>
<p>
<span class="drop-cap">{initial}</span>
<span class="first-word" style:--shift={shift}>{remainder}</span>
<slot></slot>
</p>
<span class="drop-cap">{initial}</span>
{#if remainder.length}
<span class="first-word" style:--shift={shift}>{remainder}</span>
{/if}

67
src/lib/Heading.svelte Normal file
View File

@ -0,0 +1,67 @@
<script>
export let level;
export let id = '';
const tag = `h${level}`;
</script>
<style>
a {
/* Works better to set the size here for line-height reasons */
font-size: 0.9em;
/* color: hsl(0, 0%, 25%); */
color: var(--accent-color);
}
a:hover {
border-bottom: 0.05em solid currentcolor;
}
svg {
width: 1em;
/* tiny tweak for optical alignment */
transform: translateY(2px);
}
.before {
display: none;
padding-right: 0.25em;
margin-left: -1.25em;
}
@media(min-width: 58rem) {
.before {
display: inline;
opacity: 0;
transition: opacity 150ms;
}
.h:hover .before, .before:hover {
opacity: 1;
}
.after {
display: none;
}
.h:hover {
cursor: default;
}
}
</style>
<svelte:element this={tag} {id} class="h">
<span class="before">
<a href="#{id}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" />
</svg></a></span><span> <!-- Looks ugly but necessary to get rid of spurious whitespace -->
<slot></slot>
</span>
<!-- Icon from https://heroicons.com/ -->
<a href="#{id}" class="after">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" />
</svg>
</a>
</svelte:element>

View File

@ -18,7 +18,7 @@
{#if href.startsWith('/') || host(href) === $page.host}
<a sveltekit:prefetch {href}>
<a data-sveltekit-preload-data="hover" {href}>
<slot></slot>
</a>
{:else}

View File

@ -1,8 +1,9 @@
<script context="module">
import { onMount } from 'svelte';
import { formatDate } from './datefmt.js';
import { makeSlug } from '$lib/slug.js';
import { makeSlug } from '$lib/utils.js';
import Toc from './Toc.svelte';
import Link from './Link.svelte';
export { Link as a };
</script>
@ -11,14 +12,82 @@
export let title, date;
export const description = '';
export const draft = false;
export let toc = null;
export let prev = null;
export let next = null;
</script>
<style>
.page {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, var(--content-width)) minmax(0, 1fr);
padding: 0 0.5rem;
}
.title {
grid-column: 2 / 3;
}
.left-gutter {
grid-column: 1 / 2;
justify-self: end;
}
.subtitle {
font-size: 0.9em;
font-style: italic;
margin-top: -0.5rem;
}
.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;
}
.footer a {
display: flex;
align-items: center;
gap: 0.45em;
font-size: 1rem;
color: var(--content-color-faded);
text-decoration: underline;
text-underline-offset: 0.25em;
text-decoration-color: transparent;
transition: 150ms;
}
.footer a:hover {
text-decoration-color: currentColor;
text-decoration: underline;
}
.footer svg {
width: 1em;
transition: 150ms;
}
a.prev:hover svg {
transform: translateX(-50%);
}
a.next:hover svg {
transform: translateX(50%);
}
</style>
<svelte:head>
@ -26,8 +95,40 @@
<link rel="stylesheet" href="/prism-dracula.css" />
</svelte:head>
<div id="post">
<h1 id="{makeSlug(title)}">{title}</h1>
<p class="subtitle">{formatDate(date)}</p>
<slot></slot>
<div class="page">
<div class="title">
<h1 id="{makeSlug(title)}">{title}</h1>
<p class="subtitle">{formatDate(date)}</p>
</div>
<div class="left-gutter">
<Toc items={toc} />
</div>
<div class="post">
<slot></slot>
</div>
<hr>
<div class="footer">
{#if prev}
<a href="/{prev}" class="prev" data-sveltekit-preload-data="hover">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
</svg>
Previous
</a>
{/if}
{#if next}
<!-- we use margin-left rather than justify-content so it works regardless of whether the "previous" link exists -->
<a href="/{next}" class="next" style="margin-left: auto;" data-sveltekit-preload-data="hover">
Next
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
</svg>
</a>
{/if}
</div>
</div>

View File

@ -1,4 +1,4 @@
<style lang="scss">
<style>
/* always applicable */
:global(body) {
counter-reset: sidenote;
@ -8,30 +8,30 @@
counter-increment: sidenote;
color: #444;
margin-left: 0.05rem;
}
&:after {
font-size: 0.75em;
position: relative;
bottom: 0.3rem;
color: #8c0606;
}
.counter:after {
font-size: 0.75em;
position: relative;
bottom: 0.3rem;
color: #8c0606;
}
.sidenote {
color: #555;
font-size: 0.8rem;
}
&:before {
content: 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;
}
.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;
}
.sidenote-toggle {
@ -46,16 +46,28 @@
.sidenote {
--gap: 2rem;
--sidenote-width: min(14rem, calc(50vw - var(--gap) - var(--content-width) / 2));
--sidenote-width: min(16rem, calc(50vw - var(--gap) - 1rem - var(--content-width) / 2));
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-right: calc(0rem - var(--sidenote-width) - var(--gap)); /* gives us 2rem of space between content and sidenote */
margin-bottom: 0.7rem;
}
/* fade-in animation */
.sidenote {
opacity: 0;
animation: fade-in 600ms ease-out;
animation-delay: 500ms;
animation-fill-mode: forwards;
}
@keyframes fade-in {
from {opacity: 0;}
to {opacity: 1;}
}
.nested.sidenote {
margin-right: 0;
margin-top: 0.7rem;
@ -107,26 +119,13 @@
font-size: 1.25rem;
color: #8c0606;
cursor: pointer;
&:hover {
transform: scale(1.1);
font-weight: 800;
}
}
.dismiss:hover {
transform: scale(1.1);
font-weight: 800;
}
}
// /* slight tweaks for in between state */
// @media (min-width: 52.5em) and (max-width: 70em) {
// .sidenote {
// padding-left: calc(50vw - 19rem);
// }
// }
// @media (max-width: 52.5em) {
// .sidenote {
// padding-left: 2rem;
// }
// }
</style>
<script context="module">

143
src/lib/Toc.svelte Normal file
View File

@ -0,0 +1,143 @@
<script>
import { onMount } from 'svelte';
import { makeSlug } from '$lib/utils.js';
export let items;
items.forEach(i => i.slug = makeSlug(i.text));
let headings = [];
let currentHeadingSlug = null;
let currentSubheadingSlug = null;
function setCurrentHeading() {
for (const h of headings) {
const yPos = h.getBoundingClientRect().y;
if (yPos > (window.innerHeight / 3)) {
break;
}
if (h.tagName === 'H2') {
currentHeadingSlug = h.id;
currentSubheadingSlug = null;
}
if (h.tagName === 'H3') {
currentSubheadingSlug = h.id
}
}
}
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]'));
setCurrentHeading();
});
</script>
<svelte:window on:scroll={setCurrentHeading} />
<style>
#toc {
position: sticky;
top: 1.5rem;
margin-right: 2rem;
max-width: 14rem;
color: var(--content-color-faded);
opacity: 0;
animation: fade-in 600ms ease-out;
animation-delay: 500ms;
animation-fill-mode: forwards;
}
@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 */
h5 {
font-variant: petite-caps;
font-weight: 500;
max-width: fit-content;
margin-top: 0;
margin-bottom: 0.25em;
padding-bottom: 0.25em;
border-bottom: 1px solid currentcolor;
/* make the border stretch beyond the text just a bit, because I like the effect */
padding-right: 1.5rem;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
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;
}
.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);
}
a {
color: inherit;
text-decoration: none;
}
</style>
<div id="toc">
<h5>
<span class="heading">Contents</span>
</h5>
<ul>
{#each items as item}
{#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>
</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>
</li>
{/if}
{/each}
</ul>
</div>

View File

@ -1,20 +0,0 @@
<script>
import Step from './Step.svelte';
import {onMount} from 'svelte';
let frame;
onMount(() => {
frame.setAttribute('srcdoc', frame.innerHTML);
})
</script>
<iframe bind:this={frame}>
<html>
<head></head>
<body>
<Step />
<p>Goodbye world!</p>
</body>
</html>
</iframe>

View File

@ -1,7 +0,0 @@
<script>
let count = 0;
</script>
<p>hello world!</p>
<button on:click={() => count++}>Increment</button>
<p>The count is: {count}</p>

View File

@ -1,50 +0,0 @@
const nonAlphaNum = /[^A-Za-z0-9\-]/g;
const space = /\s/g
export function makeSlug(text) {
return text
.toLowerCase()
.replace(space, '-')
.replace(nonAlphaNum, '')
}
function apply(node, types, fn) {
if (typeof types === 'string') {
types = new Set([types]);
}
else if (!(types instanceof Set)) {
types = new Set(types)
console.log(types)
}
if (types.has(node.type)) {
fn(node);
}
if ('children' in node) {
for (let child of node.children) {
apply(child, types, fn);
}
}
}
function getTextContent(node) {
let segments = [];
apply(node, 'text', textNode => {
// skip all-whitespace strings
if (textNode.value.match(/^\s+$/)) return;
segments.push(textNode.value.trim());
});
return segments.join(' ');
}
export default function slug() {
return (tree) => {
apply(tree, 'element', e => {
if (e.tagName.match(/h[1-6]/)) {
let text = getTextContent(e);
e.properties.id = makeSlug(text);
}
})
}
}

8
src/lib/utils.js Normal file
View File

@ -0,0 +1,8 @@
const nonAlphaNum = /[^A-Za-z0-9\-]/g;
const space = /\s+/g;
export function makeSlug(text) {
return text
.toLowerCase()
.replace(space, '-')
.replace(nonAlphaNum, '');
}

94
src/lib/xml.js Normal file
View File

@ -0,0 +1,94 @@
// const Node = {
// addChild(child) {
// this.children.push(child);
// return child;
// }
// }
export function tag(name, attrs, children) {
return {
type: 'tag',
tag: name,
attrs: attrs || {},
children: children || [],
addTag(name, attrs, children) {
const child = tag(name, attrs, children);
this.children.push(child);
return child;
},
};
}
export function text(content) {
return {
type: 'text',
text: content,
};
}
export function serialize(node, depth) {
if (!depth) {
depth = 0;
}
const indent = ' '.repeat(depth * 4);
let fragments = [];
// version tag, if this is the top level
if (depth === 0) {
fragments.push('<?xml version="1.0" encoding="UTF-8"?>\n')
}
fragments.push(`${indent}<${node.tag}`);
// this happens if there are multiple text nodes within the same parent
if (node.type === 'text') {
return `${indent}${escape(node.text)}`;
}
if (node.children === undefined) {
console.log(node);
}
// opening tag <element attr="value">
for (const attr in node.attrs) {
fragments.push(` ${attr}="${node.attrs[attr]}"`);
}
if (node.children.length === 0) {
fragments.push(' />');
return fragments.join('');
}
fragments.push('>');
// if the only child is a single text node, skip recursion and just dump contents directly
if (node.children.length === 1 && node.children[0].type === 'text') {
const text = escape(node.children[0].text);
fragments.push(text);
}
// otherwise, start a new line for each child node, then recurse
else {
for (const child of node.children) {
fragments.push('\n');
fragments.push(serialize(child, depth + 1));
}
// no need to verify that there were children, we already did that
fragments.push(`\n${indent}`);
}
fragments.push(`</${node.tag}>`);
return fragments.join('');
}
function escape(text) {
// we aren't going to bother with escaping attributes, so we won't worry about quotes
return text
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;');
}

90
src/plugins/rehype.js Normal file
View File

@ -0,0 +1,90 @@
import { visit, CONTINUE, EXIT, SKIP, } from 'unist-util-visit';
import { find } from 'unist-util-find';
import { toText } from 'hast-util-to-text';
import { makeSlug } from '../lib/utils.js';
export function localRehype() {
let printed = false;
return (tree, vfile) => {
const needsDropcap = vfile.data.fm.dropcap !== false
let dropcapAdded = false;
let moduleScript;
let imports = new Set();
if (needsDropcap) {
imports.add("import Dropcap from '$lib/Dropcap.svelte';");
}
visit(tree, node => {
// add slugs to headings
if (isHeading(node)) {
processHeading(node);
imports.add("import Heading from '$lib/Heading.svelte';");
return SKIP;
}
// mdsvex adds a <script context="module"> so we just hijack that for our own purposes
if (isModuleScript(node)) {
moduleScript = node;
}
// convert first letter/word of first paragraph to <Dropcap word="{whatever}">
if (needsDropcap && !dropcapAdded && isParagraph(node)) {
addDropcap(node);
dropcapAdded = true;
return SKIP;
}
});
// insert our imports at the top of the `<script context="module">` tag
if (imports.size > 0) {
const script = moduleScript.value;
// split the script where the opening tag ends
const i = script.indexOf('>');
const openingTag = script.slice(0, i + 1);
const remainder = script.slice(i + 1);
// mdvsex uses tabs so we will as well
const importScript = Array.from(imports).join('\n\t');
moduleScript.value = `${openingTag}\n\t${importScript}${remainder}`;
}
}
}
function processHeading(node) {
const level = node.tagName.slice(1);
node.tagName = 'Heading';
node.properties.level = level;
node.properties.id = makeSlug(toText(node));
}
function addDropcap(par) {
let txtNode = find(par, {type: 'text'});
const i = txtNode.value.search(/\s/);
const firstWord = txtNode.value.slice(0, i);
const remainder = txtNode.value.slice(i);
par.children.unshift({
type: 'raw',
value: `<Dropcap word="${firstWord}" />`,
});
txtNode.value = remainder;
}
function isHeading(node) {
return node.type === 'element' && node.tagName.match(/h[1-6]/);
}
function isModuleScript(node) {
return node.type === 'raw' && node.value.match(/^<script context="module">/);
}
function isParagraph(node) {
return node.type === 'element' && node.tagName === 'p';
}

48
src/plugins/remark.js Normal file
View File

@ -0,0 +1,48 @@
import { visit } from 'unist-util-visit';
import { toString } from 'mdast-util-to-string';
import fs from 'node:fs';
// build table of contents and inject into frontmatter
export function localRemark() {
return (tree, vfile) => {
let toc = [];
let description = null;
visit(tree, ['heading', 'paragraph'], node => {
// build table of contents and inject into frontmatter
if (node.type === 'heading') {
toc.push({
text: toString(node),
depth: node.depth,
});
}
// inject description (first 25 words of the first paragraph)
if (node.type === 'paragraph' && description === null) {
description = summarize(node);
}
});
vfile.data.fm.toc = toc;
vfile.data.fm.description = description;
}
}
// convert paragraph to single string after stripping everything between html tags
function summarize(par) {
let newChildren = [];
let push = true;
for (const child of par.children) {
if (child.type === 'html') {
push = !push;
continue;
}
if (push) {
newChildren.push(child);
}
}
return toString({type: 'paragraph', children: newChildren});
}

1
src/routes/+layout.js Normal file
View File

@ -0,0 +1 @@
export const prerender = true;

39
src/routes/+layout.svelte Normal file
View File

@ -0,0 +1,39 @@
<style>
.header {
grid-column-start: 1;
grid-column-end: 4;
background-color: #4f5f68;
}
nav {
max-width: 30rem;
margin: 0 auto;
display: flex;
justify-content: space-around;
}
nav a {
width: 8rem;
min-width: 6rem;
font-size: 1.5rem;
color: white;
text-decoration: none;
text-align: center;
padding: 0.25rem 0;
}
nav a:hover {
background-color: #00000025;
}
</style>
<div class="header">
<nav>
<a data-sveltekit-preload-data="hover" href="/">Home</a>
<a data-sveltekit-preload-data="hover" href="/posts">Posts</a>
<a data-sveltekit-preload-data="hover" href="/">About</a>
</nav>
</div>
<main>
<slot></slot>
</main>

7
src/routes/+page.js Normal file
View File

@ -0,0 +1,7 @@
export async function load({ data }) {
let post = await import(`./_posts/${data.slug}.svx`);
post.metadata.next = data.next;
return {
post: post.default,
}
}

View File

@ -0,0 +1,11 @@
import { postData, siblingPosts } from './_posts/all.js';
// this is in a "servserside" loader so that we don't end up embedding the metadata
// for every post into the final page
export function load() {
return {
slug: postData[0].slug,
next: postData[1].slug,
};
}

5
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,5 @@
<script>
export let data;
</script>
<svelte:component this={data.post} />

View File

@ -1,24 +0,0 @@
<script context="module">
export async function load({ url, params }) {
try {
let post = await import(`./_posts/${params.slug}.svx`);
return {
props: {
post: post.default
}
}
}
catch (err) {
return {
status: 404,
error: `Not found: ${url.pathname}`,
}
}
}
</script>
<script>
export let post;
</script>
<svelte:component this={post} />

View File

@ -0,0 +1,18 @@
import { error } from '@sveltejs/kit';
export async function load({ url, params, data }) {
try {
let post = await import(`../_posts/${params.slug}.svx`);
post.metadata.prev = data.prev;
post.metadata.next = data.next;
return {
post: post.default,
}
}
catch (err) {
// throw error(404, `Not found: ${url.pathname}`);
console.log(err);
throw err;
}
}

View File

@ -0,0 +1,10 @@
import { postData } from '../_posts/all.js';
export function load({ params }) {
const i = postData.findIndex(p => p.slug === params.slug);
return {
prev: i > 0 ? postData[i - 1].slug : null,
next: i < postData.length - 1 ? postData[i + 1].slug : null,
};
}

View File

@ -0,0 +1,5 @@
<script>
export let data;
</script>
<svelte:component this={data.post} />

View File

@ -1,45 +0,0 @@
<style>
:global(main) {
--content-width: 42rem;
box-sizing: border-box;
max-width: var(--content-width);
margin: 0 auto;
padding: 0 15px;
}
#header {
background-color: #4f5f68;
}
#nav-main {
max-width: 30rem;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(3, minmax(6rem, 8rem));
justify-content: space-between;
}
#nav-main a {
font-size: 1.5rem;
color: white;
text-decoration: none;
text-align: center;
padding: 0.25rem 0;
}
#nav-main a:hover {
background-color: #00000025;
}
</style>
<div id="header">
<nav id="nav-main">
<a sveltekit:prefetch href="/">Home</a>
<a sveltekit:prefetch href="/posts">Posts</a>
<a sveltekit:prefetch href="/">About</a>
</nav>
</div>
<main>
<slot></slot>
</main>

View File

@ -1,28 +1,26 @@
import { dev } from '$app/env';
const posts = import.meta.globEager('./_posts/*.svx');
import { dev } from '$app/environment';
const posts = import.meta.globEager('./*.svx');
export let postData = [];
let postData = [];
for (const path in posts) {
// skip draft posts in production mode
if (!dev && posts[path].metadata.draft) {
continue;
}
const slug = path.slice(9, -4)
// slice off the ./ and the .svx
const slug = path.slice(2, -4);
posts[path].metadata.slug = slug;
postData.push(posts[path].metadata);
}
postData.sort((a, b) => {
// sorting in reverse, so we flip the intuitive order
if (a.date > b.date) return -1;
if (a.date < b.date) return 1;
return 0;
})
export async function get() {
return {
body: {postData}
};
}
});
export { postData };

View File

@ -3,6 +3,7 @@ title: Imagining A Passwordless Future
description: Can we replace passwords with something more user-friendly?
date: 2021-04-30
draft: true
dropcap: false
---
<script>
import Sidenote from '$lib/Sidenote.svelte';

View File

@ -4,10 +4,8 @@ description: An entirely-too-detailed dive into how I implemented sidenotes for
date: 2023-08-14
---
<script>
import Dropcap from '$lib/Dropcap.svelte';
import Sidenote from '$lib/Sidenote.svelte';
import UnstyledSidenote from '$lib/UnstyledSidenote.svelte';
import Frame from '$lib/projects/sidenotes/Frame.svelte';
</script>
<style>
@ -64,7 +62,7 @@ date: 2023-08-14
}
</style>
<Dropcap word="One">of my major goals when building this blog was to have sidenotes. I've always been a fan of sidenotes on the web, because the most comfortable reading width for a column of text is <em>far</em> less than the absurd amounts of screen width we tend to have available, and what else are we going to use it for?<Sidenote>Some sites use it for ads, of course, which is yet another example of how advertising ruins everything.</Sidenote></Dropcap>
One of my major goals when building this blog was to have sidenotes. I've always been a fan of sidenotes on the web, because the most comfortable reading width for a column of text is <em>far</em> less than the absurd amounts of screen width we tend to have available, and what else are we going to use it for?<Sidenote>Some sites use it for ads, of course, which is yet another example of how advertising ruins everything.</Sidenote>
Footnotes don't really work on the web the way they do on paper, since the web doesn't have page breaks. You _can_ stick your footnotes in a floating box at the bottom of the page, so they're visible at the bottom of the text just like they would be on a printed page, but this sacrifices precious vertical space.<Sidenote>On mobile, it's _horizontal_ space that's at a premium, so I do use this approach there. Although I'm a pretty heavy user of sidenotes, so I have to make them toggleable as well or they'd fill up the entire screen.</Sidenote> Plus, you usually end up with the notes further away from the point of divergence than they would be as sidenotes anyway.

View File

@ -4,12 +4,11 @@ description: They're more similar than they are different, but they say the most
date: 2023-06-29
---
<script>
import Dropcap from '$lib/Dropcap.svelte';
import Sidenote from '$lib/Sidenote.svelte';
</script>
<Dropcap word="Recently">I've had a chance to get to know Vue a bit. Since my frontend framework of choice has previously been Svelte (this blog is built in Svelte, for instance) I was naturally interested in how they compared.</Dropcap>
Recently I've had a chance to get to know Vue a bit. Since my frontend framework of choice has previously been Svelte (this blog is built in Svelte, for instance) I was naturally interested in how they compared.
Of course, this is only possible because Vue and Svelte are really much more similar than they are different. Even among frontend frameworks, they share a lot of the same basic ideas and high-level concepts, which means that we get to dive right into the nitpicky details and have fun debating `bind:attr={value}` versus `:attr="value"`. In the meantime, a lot of the building blocks are basically the same or at least have equivalents, such as:
* Single-file components with separate sections for markup, style, and logic

View File

@ -0,0 +1,51 @@
import { tag, text, serialize } from '$lib/xml.js';
import { postData } from '../_posts/all.js';
export function GET() {
return new Response(renderFeed(), {
headers: {'Content-Type': 'application/atom+xml'}
});
}
function renderFeed() {
const feed = tag('feed', {xmlns: 'http://www.w3.org/2005/Atom'});
feed.addTag('id', {}, [text('https://blog.jfmonty2.com/')])
feed.addTag('title', {}, [text("Joe's Blog")]);
feed.addTag('link', {rel: 'alternate', href: 'https://blog.jfmonty2.com/'});
feed.addTag('link', {rel: 'self', href: 'https://blog.jfmonty2.com/feed/'});
const lastUpdate = iso(postData[0].updated || postData[0].date);
feed.addTag('updated', {}, [text(lastUpdate)]);
const author = feed.addTag('author');
author.addTag('name', {}, [text('Joseph Montanaro')]);
for (const post of postData) {
const url = `https://blog.jfmonty2.com/${post.slug}`
const entry = feed.addTag('entry');
entry.addTag('title', {}, [text(post.title)]);
entry.addTag('link', {rel: 'alternate', href: url});
entry.addTag('id', {}, [text(url)]);
const publishedDate = iso(post.date);
entry.addTag('published', {}, [text(publishedDate)])
const updatedDate = iso(post.updated || post.date);
entry.addTag('updated', {}, [text(updatedDate)]);
entry.addTag('content', {type: 'html'}, [text(renderDescription(post))]);
}
return serialize(feed);
}
function renderDescription(post) {
return `<p>${post.description} <a href="https://blog.jfmonty2.com/${post.slug}">Read more</a></p>`;
}
function iso(datetimeStr) {
return new Date(datetimeStr).toISOString();
}

View File

@ -1,18 +0,0 @@
<script context="module">
export async function load({ fetch }) {
const resp = await fetch('/latest.json');
const postMeta = await resp.json();
const post = await import(`./_posts/${postMeta.slug}.svx`);
return {
props: {
post: post.default,
}
}
}
</script>
<script>
export let post;
</script>
<svelte:component this={post} />

View File

@ -1,5 +0,0 @@
import { postData } from './posts.js';
export async function get() {
return {body: postData[0]};
}

View File

@ -1,27 +1,20 @@
<script>
import { formatDate } from '$lib/datefmt.js';
export let postData;
import { postData } from '../_posts/all.js';
</script>
<style lang="scss">
<style>
#posts {
/*text-align: center;*/
max-width: 24rem;
// margin-top: 1.25rem;
margin-left: auto;
margin-right: auto;
max-width: var(--content-width);
margin: 0 auto;
}
.post {
border-bottom: 2px solid #eee;
margin-top: 1rem;
hr {
margin: 2rem 0;
border-color: #eee;
}
/* .post-title {
font-weight: bold;
font-size: 1.2rem;
}*/
.post-date {
color: #808080;
}
@ -44,9 +37,14 @@
text-decoration: underline;
}
h3 {
display: inline;
margin: 0;
h2 {
font-size: 1.25rem;
margin-top: 0.5rem;
margin-bottom: 0.75rem;
}
h2 a {
color: currentcolor;
}
</style>
@ -56,18 +54,22 @@
<div id="posts">
<h1 style:text-align="center">All Posts</h1>
{#each postData as post}
{#each postData as post, idx}
<div class="post">
<div class="post-date">{new Date(post.date).toISOString().split('T')[0]}</div>
<div>
<a sveltekit:prefetch class="post-link" href="/{post.slug}">
<h3>{post.title}<h3>
<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}
</div>
</h2>
<p>{post.description}</p>
</div>
{#if idx < postData.length - 1}
<hr>
{/if}
{/each}
</div>

View File

@ -29,10 +29,12 @@ html {
line-height: var(--content-line-height);
letter-spacing: -0.005em;
color: var(--content-color);
box-sizing: border-box;
}
body {
margin: 0;
--content-width: 42rem;
}
h1, h2, h3, h4, h5, h6 {

View File

@ -1,24 +1,24 @@
import { mdsvex } from 'mdsvex';
import staticAdapter from '@sveltejs/adapter-static';
import svp from 'svelte-preprocess';
import slug from './src/lib/slug.js';
import { mdsvex } from 'mdsvex';
import { localRemark } from './src/plugins/remark.js';
import { localRehype } from './src/plugins/rehype.js';
/** @type {import('@sveltejs/kit').Config} */
const config = {
extensions: ['.svelte', '.svx'],
preprocess: [
mdsvex({
layout: './src/lib/Post.svelte',
rehypePlugins: [slug],
remarkPlugins: [localRemark],
rehypePlugins: [localRehype],
}),
svp.scss(),
],
kit: {
// hydrate the <div id="svelte"> element in src/app.html
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: staticAdapter(),
prerender: {
default: true,
},
}
};

View File

@ -1,281 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<!-- Crane image created by macrovector on Freepik: https://www.freepik.com/free-vector/construction-icons-set_1537228.htm#query=crane&position=3&from_view=keyword -->
<svg
width="28.305676mm"
height="28.174238mm"
viewBox="0 0 28.305676 28.174238"
version="1.1"
id="svg1392"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
sodipodi:docname="crane.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1394"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.1089995"
inkscape:cx="-5.6899017"
inkscape:cy="78.710306"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1389" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-139.84716,-103.71933)">
<path
d="m 166.79605,124.82179 h 0.18627 v -20.83188 h -0.18627 v 20.83188"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path324" />
<path
d="m 166.25101,125.97184 h 1.27635 v -0.51893 c 0,-0.1323 -0.10724,-0.23919 -0.23918,-0.23919 h -0.79763 c -0.13229,0 -0.23954,0.10689 -0.23954,0.23919 v 0.51893"
style="fill:#e4731a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path326" />
<path
d="m 166.6934,125.21372 h 0.39193 v -0.64981 h -0.39193 v 0.64981"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path328" />
<path
d="m 165.62554,126.85626 c 0,0.69814 0.56585,1.26365 1.26365,1.26365 0.69779,0 1.26365,-0.56551 1.26365,-1.26365 0,-0.6978 -0.56586,-1.26365 -1.26365,-1.26365 -0.6978,0 -1.26365,0.56585 -1.26365,1.26365"
style="fill:#f9a727;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path330" />
<path
d="m 143.76637,124.8673 19.84057,-20.49675 -0.0935,-0.0903 -19.84093,20.49639 0.0938,0.0907"
style="fill:#100f0d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path332" />
<path
d="m 149.82885,128.1506 2.90478,-6.56802 10.82675,-17.25718 1.80869,1.13488 -10.81899,17.2466 -4.61751,5.50863 z m 13.67261,-24.08167 -10.93188,17.42652 -2.97638,6.72711 0.37782,0.23707 4.72899,-5.64338 10.92553,-17.41488 -2.12408,-1.33244"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path334" />
<path
d="m 154.67073,122.68783 -0.0988,0.1577 -1.96638,-1.23367 0.0988,-0.15769 1.96638,1.23366"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path336" />
<path
d="m 154.712,122.74604 -0.18132,0.0413 -0.71791,-3.13443 0.18168,-0.0416 0.71755,3.13478"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path338" />
<path
d="m 155.85042,120.93135 -3.13443,0.71755 -0.0416,-0.18132 3.13478,-0.71791 0.0413,0.18168"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path340" />
<path
d="m 155.87935,120.76167 -0.0991,0.15769 -1.96639,-1.23366 0.0991,-0.1577 1.96639,1.23367"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path342" />
<path
d="m 155.92062,120.81952 -0.18168,0.0416 -0.71755,-3.13443 0.18133,-0.0416 0.7179,3.13443"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path344" />
<path
d="m 157.05903,119.00483 -3.13478,0.71791 -0.0413,-0.18133 3.13443,-0.7179 0.0416,0.18132"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path346" />
<path
d="m 157.08761,118.8355 -0.0988,0.15769 -1.96638,-1.23366 0.0988,-0.15769 1.96638,1.23366"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path348" />
<path
d="m 157.12888,118.89336 -0.18132,0.0416 -0.71791,-3.13478 0.18168,-0.0413 0.71755,3.13443"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path350" />
<path
d="m 158.2673,117.07867 -3.13443,0.7179 -0.0416,-0.18168 3.13478,-0.71755 0.0413,0.18133"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path352" />
<path
d="m 158.29623,116.90898 -0.0991,0.15769 -1.96639,-1.23331 0.0991,-0.15804 1.96639,1.23366"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path354" />
<path
d="m 158.3375,116.96719 -0.18168,0.0416 -0.71755,-3.13479 0.18133,-0.0413 0.7179,3.13443"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path356" />
<path
d="m 159.47592,115.1525 -3.13479,0.7179 -0.0413,-0.18168 3.13443,-0.71755 0.0416,0.18133"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path358" />
<path
d="m 159.50449,114.98282 -0.0988,0.15804 -1.96638,-1.23366 0.0988,-0.15805 1.96638,1.23367"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path360" />
<path
d="m 159.54577,115.04102 -0.18133,0.0416 -0.71791,-3.13478 0.18169,-0.0413 0.71755,3.13443"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path362" />
<path
d="m 160.68418,113.22633 -3.13443,0.71791 -0.0416,-0.18168 3.13478,-0.71755 0.0413,0.18132"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path364" />
<path
d="m 160.71311,113.05665 -0.0991,0.15769 -1.96639,-1.23331 0.0991,-0.15805 1.96639,1.23367"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path366" />
<path
d="m 160.75438,113.11486 -0.18168,0.0416 -0.7179,-3.13478 0.18168,-0.0413 0.7179,3.13443"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path368" />
<path
d="m 161.8928,111.30017 -3.13479,0.7179 -0.0413,-0.18168 3.13443,-0.71755 0.0416,0.18133"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path370" />
<path
d="m 161.92137,111.13048 -0.0991,0.15805 -1.96603,-1.23367 0.0988,-0.15804 1.96638,1.23366"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path372" />
<path
d="m 161.96265,111.18869 -0.18133,0.0416 -0.71791,-3.13479 0.18169,-0.0416 0.71755,3.13478"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path374" />
<path
d="m 163.10106,109.374 -3.13443,0.7179 -0.0416,-0.18168 3.13478,-0.7179 0.0413,0.18168"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path376" />
<path
d="m 163.12999,109.20432 -0.0991,0.15769 -1.96639,-1.23367 0.0991,-0.15769 1.96639,1.23367"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path378" />
<path
d="m 163.17126,109.26252 -0.18168,0.0413 -0.71755,-3.13443 0.18133,-0.0416 0.7179,3.13478"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path380" />
<path
d="m 164.30968,107.44783 -3.13479,0.71755 -0.0413,-0.18132 3.13443,-0.71791 0.0416,0.18168"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path382" />
<path
d="m 164.33825,107.27815 -0.0988,0.15769 -1.96638,-1.23366 0.0988,-0.1577 1.96638,1.23367"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path384" />
<path
d="m 164.37953,107.33636 -0.18133,0.0413 -0.7179,-3.13443 0.18168,-0.0416 0.71755,3.13479"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path386" />
<path
d="m 165.51794,105.52167 -3.13443,0.71755 -0.0416,-0.18133 3.13443,-0.7179 0.0416,0.18168"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path388" />
<path
d="m 153.38944,124.32366 -0.18133,0.0416 -0.64382,-2.81128 0.18133,-0.0416 0.64382,2.81128"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path390" />
<path
d="m 153.34817,124.26581 -0.0988,0.15769 -1.48908,-0.93415 0.0991,-0.1577 1.48873,0.93416"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path392" />
<path
d="m 154.64215,122.85752 -2.81163,0.64382 -0.0413,-0.18168 2.81129,-0.64382 0.0416,0.18168"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path394" />
<path
d="m 152.06476,125.91399 -0.18556,0.012 -0.16228,-2.50931 0.18591,-0.0123 0.16193,2.50966"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path396" />
<path
d="m 152.02137,125.84096 -0.0988,0.15769 -0.98954,-0.62053 0.0991,-0.15769 0.98919,0.62053"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path398" />
<path
d="m 153.33405,124.43056 -2.31598,0.95461 -0.0709,-0.17215 2.31599,-0.95462 0.0709,0.17216"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path400" />
<path
d="m 167.06769,103.88196 c -0.20602,-0.12912 -0.44944,-0.1838 -0.69074,-0.15523 l -2.87549,0.3422 -0.0935,0.16122 2.09691,1.31551 1.58573,-1.32151 c 0.0522,-0.0434 0.0801,-0.10936 0.0759,-0.17709 -0.005,-0.0677 -0.0416,-0.12912 -0.0988,-0.1651"
style="fill:#e4731a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path402" />
<path
d="m 163.77733,104.24179 2.71922,-0.33902 c 0.13687,-0.0169 0.27551,0.0138 0.39264,0.0871 l 0.0395,0.0247 c 0.0162,0.0102 0.0265,0.0275 0.0279,0.0466 0.001,0.019 -0.007,0.0378 -0.0215,0.0501 l -1.44533,1.20474 -1.71239,-1.07421"
style="fill:#f9a727;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path404" />
<path
d="m 145.63997,128.61168 h 3.81459 v 0.62547 h -3.81459 v -0.62547"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path406" />
<path
d="m 140.21425,126.26888 c 0.0783,-0.35489 0.34149,-0.63994 0.68897,-0.74683 l 6.68832,-2.05246 -0.3549,5.14209 h -6.77756 c -0.18556,0 -0.3609,-0.084 -0.47696,-0.2286 -0.11606,-0.14429 -0.16051,-0.33373 -0.12065,-0.5147 l 0.35278,-1.5995"
style="fill:#f9a727;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path408" />
<path
d="m 151.90037,131.89357 c 0.75917,0 1.37689,-0.61771 1.37689,-1.37724 0,-0.75918 -0.61772,-1.3769 -1.37689,-1.3769 -0.0243,-0.002 -3.12667,-0.22013 -4.15961,-0.22013 -1.03293,0 -4.13526,0.21802 -4.1663,0.22049 h -7.1e-4 c -0.75212,0 -1.36948,0.61736 -1.36948,1.37654 0,0.75953 0.61771,1.37724 1.37689,1.37724 h 8.31921"
style="fill:#252529;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path410" />
<path
d="m 143.58116,131.69778 c -0.65158,0 -1.18145,-0.52987 -1.18145,-1.18145 0,-0.65123 0.52669,-1.1811 1.17475,-1.1811 l 0.0141,-7.1e-4 c 0.03,-0.002 3.12667,-0.21943 4.15219,-0.21943 1.03082,0 4.11551,0.21696 4.14338,0.21908 l 0.008,7e-4 h 0.008 c 0.65158,0 1.18145,0.53023 1.18145,1.18146 0,0.65158 -0.52987,1.18145 -1.18145,1.18145 h -8.31921"
style="fill:#e4731a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path412" />
<path
d="m 150.71891,130.51633 c 0,-0.65229 0.52882,-1.18146 1.18146,-1.18146 0.65263,0 1.18145,0.52917 1.18145,1.18146 0,0.65263 -0.52882,1.18145 -1.18145,1.18145 -0.65264,0 -1.18146,-0.52882 -1.18146,-1.18145"
style="fill:#b8bbb6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path414" />
<path
d="m 151.90037,131.19507 c 0.37429,0 0.67874,-0.30445 0.67874,-0.67874 0,-0.3743 -0.30445,-0.67875 -0.67874,-0.67875 -0.3743,0 -0.67875,0.30445 -0.67875,0.67875 0,0.37429 0.30445,0.67874 0.67875,0.67874"
style="fill:#252529;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path416" />
<path
d="m 145.53555,129.58146 c 0,-0.19014 0.15416,-0.34431 0.34431,-0.34431 0.19015,0 0.34431,0.15417 0.34431,0.34431 0,0.19015 -0.15416,0.34432 -0.34431,0.34432 -0.19015,0 -0.34431,-0.15417 -0.34431,-0.34432"
style="fill:#252529;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path418" />
<path
d="m 145.54331,131.35311 c 0,-0.19014 0.15416,-0.34431 0.34431,-0.34431 0.19015,0 0.34396,0.15417 0.34396,0.34431 0,0.19015 -0.15381,0.34432 -0.34396,0.34432 -0.19015,0 -0.34431,-0.15417 -0.34431,-0.34432"
style="fill:#252529;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path420" />
<path
d="m 149.25735,131.35311 c 0,-0.19014 0.15417,-0.34431 0.34432,-0.34431 0.19014,0 0.34431,0.15417 0.34431,0.34431 0,0.19015 -0.15417,0.34432 -0.34431,0.34432 -0.19015,0 -0.34432,-0.15417 -0.34432,-0.34432"
style="fill:#252529;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path422" />
<path
d="m 149.94598,129.58146 c 0,-0.19014 -0.15417,-0.34431 -0.34431,-0.34431 -0.19015,0 -0.34432,0.15417 -0.34432,0.34431 0,0.19015 0.15417,0.34432 0.34432,0.34432 0.19014,0 0.34431,-0.15417 0.34431,-0.34432"
style="fill:#252529;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path424" />
<path
d="m 144.76261,130.51633 c 0,-0.65229 -0.52881,-1.18146 -1.18145,-1.18146 -0.65264,0 -1.18145,0.52917 -1.18145,1.18146 0,0.65263 0.52881,1.18145 1.18145,1.18145 0.65264,0 1.18145,-0.52882 1.18145,-1.18145"
style="fill:#b8bbb6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path426" />
<path
d="m 143.58116,131.19507 c 0.3743,0 0.67874,-0.30445 0.67874,-0.67874 0,-0.3743 -0.30444,-0.67875 -0.67874,-0.67875 -0.3743,0 -0.67874,0.30445 -0.67874,0.67875 0,0.37429 0.30444,0.67874 0.67874,0.67874"
style="fill:#252529;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path428" />
<path
d="m 143.43652,131.0088 h 8.60848 v -0.98495 h -8.60848 v 0.98495"
style="fill:#f9a727;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path430" />
<path
d="m 147.23664,128.61168 h 3.55812 c 0.33761,0 0.61172,-0.27376 0.61172,-0.61172 v -0.73236 c 0,-0.72073 0.11465,-2.63737 -0.76377,-3.69429 -0.0744,-0.0896 -0.18485,-0.14147 -0.30092,-0.14147 h -2.60173 c -0.16158,0 -0.29704,0.12277 -0.31256,0.28364 l -0.19086,1.97943 v 2.91677"
style="fill:#e4731a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path432" />
<path
d="m 150.76301,128.2649 c 0.16969,0 0.30797,-0.13794 0.30797,-0.30762 v -0.7165 c 0,-0.066 7.1e-4,-0.14217 0.002,-0.22648 0.01,-0.76165 0.03,-2.34562 -0.68192,-3.20216 -0.0173,-0.0212 -0.0437,-0.0335 -0.0709,-0.0335 h -2.54529 c -0.008,0 -0.0155,0.006 -0.0162,0.0148 l -0.16404,1.69827 c -0.0141,0.14464 0.025,0.28928 0.10971,0.40711 l 1.52153,2.11702 c 0.11219,0.15628 0.29316,0.24906 0.48578,0.24906 h 1.05163"
style="fill:#4c5462;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path434" />
<path
d="m 141.47331,125.91928 h 2.36891 c 0.0723,0 0.13052,0.0924 0.13052,0.20637 h -0.13052 -0.13053 -2.10785 -0.13053 -0.13053 c 0,-0.11394 0.0582,-0.20637 0.13053,-0.20637"
style="fill:#e4731a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path436" />
<path
d="m 141.47331,126.34649 h 2.36891 c 0.0723,0 0.13052,0.0924 0.13052,0.20673 h -0.13052 -0.13053 -2.10785 -0.13053 -0.13053 c 0,-0.1143 0.0582,-0.20673 0.13053,-0.20673"
style="fill:#e4731a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path438" />
<path
d="m 141.47331,126.77406 h 2.36891 c 0.0723,0 0.13052,0.0924 0.13052,0.20637 h -0.13052 -0.13053 -2.10785 -0.13053 -0.13053 c 0,-0.11394 0.0582,-0.20637 0.13053,-0.20637"
style="fill:#e4731a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path440" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,99 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Under Construction</title>
<style>
body {
margin: 0;
}
main {
background-color: #f2f2f2;
padding: 1rem;
height: 100vh;
width: 100vw;
justify-content: center;
align-content: center;
display: grid;
}
#hero {
padding: 4rem;
background-color: white;
border-radius: 100%;
}
#hero img {
width: 16rem;
}
p {
font-family: sans-serif;
margin-bottom: 2rem;
margin-top: 2rem;
font-size: 1.5rem;
text-align: center;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/3.0.4/luxon.min.js" integrity="sha512-XdACFfCJeqqfVU8mvvXReyFR130qjFvfv/PZOFGwVyBz0HC+57fNkSacMPF2Dyek5jqi4D7ykFrx/T7N6F2hwQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<main>
<p style="font-size:2.5rem;color:#505050">Coming Soon&trade;</p>
<div id="hero">
<img src="/crane.svg">
</div>
<p>
Under Construction for <br />
<span id="counter" style="margin-top:0.5rem"></span>
</p>
</main>
</body>
<script>
function u(v, unit) {
if (v === 1) {
return `${v} ${unit}`;
}
else {
return `${v} ${unit}s`;
}
}
function f(n) {
let s = n.toString();
if (s.length == 1) {
return '0' + s;
}
return s;
}
const start = luxon.DateTime.fromSeconds(1634529923);
function setDuration() {
var diff = luxon.DateTime.now().diff(start);
const years = Math.floor(diff.as('years'));
diff = diff.minus(luxon.Duration.fromObject({years}));
const months = Math.floor(diff.as('months'));
diff = diff.minus(luxon.Duration.fromObject({months}));
const days = Math.floor(diff.as('days'));
diff = diff.minus(luxon.Duration.fromObject({days}));
const hours = Math.floor(diff.as('hours'))
diff = diff.minus(luxon.Duration.fromObject({hours}));
const minutes = Math.floor(diff.as('minutes'));
diff = diff.minus(luxon.Duration.fromObject({minutes}));
const seconds = Math.floor(diff.as('seconds'));
diff = diff.minus(luxon.Duration.fromObject({seconds}));
const millis = diff.as('milliseconds');
const timeString = `${u(years, "year")}, ${u(months, "month")}, ${u(days, "day")}, ${f(hours)}:${f(minutes)}:${f(seconds)}.${Math.floor(millis / 100)}`;
document.getElementById('counter').innerHTML = timeString;
window.setTimeout(setDuration, 10);
}
setDuration();
</script>
</html>

6
vite.config.js Normal file
View File

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});