Compare commits

..

3 Commits

Author SHA1 Message Date
b4a1097845 buncha stuff 2023-08-16 21:45:48 -07:00
adc582116b wip sidenotes post 2023-08-15 11:48:07 -07:00
7d5c696fa7 start working on sidenotes post 2023-08-15 11:10:22 -07:00
49 changed files with 6434 additions and 2963 deletions

5
.gitignore vendored
View File

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

2
.npmrc
View File

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

View File

@ -1,6 +1,6 @@
# create-svelte # 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 ## Creating a project
@ -8,12 +8,14 @@ If you're seeing this, you've probably already done this step. Congrats!
```bash ```bash
# create a new project in the current directory # create a new project in the current directory
npm create svelte@latest npm init svelte@next
# create a new project in my-app # create a new project in my-app
npm create svelte@latest my-app npm init svelte@next my-app
``` ```
> Note: the `@next` is temporary
## Developing ## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
@ -27,12 +29,10 @@ npm run dev -- --open
## Building ## Building
To create a production version of your app: Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
```bash ```bash
npm run build npm run build
``` ```
You can preview the production build with `npm run preview`. > 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.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

11
jsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"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,23 +1,18 @@
{ {
"name": "blog.jfmonty2.com", "name": "blog",
"version": "0.0.1", "version": "0.0.1",
"private": true, "scripts": {
"scripts": { "dev": "svelte-kit dev",
"dev": "vite dev", "build": "svelte-kit build",
"build": "vite build", "preview": "svelte-kit preview"
"preview": "vite preview" },
}, "devDependencies": {
"devDependencies": { "@sveltejs/adapter-static": "^1.0.0-next.21",
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "next",
"@sveltejs/adapter-static": "^2.0.3", "mdsvex": "^0.9.8",
"@sveltejs/kit": "^1.20.4", "node-sass": "^6.0.1",
"hast-util-to-text": "^4.0.0", "svelte": "^3.42.6",
"mdast-util-to-string": "^4.0.0", "svelte-preprocess": "^4.9.8"
"mdsvex": "^0.11.0", },
"svelte": "^4.0.5", "type": "module"
"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" /> <meta charset="utf-8" />
<link rel="preload" href="/Tajawal-Regular.woff2" as="font" type="font/woff2" /> <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="preload" href="/Baskerville-Regular.woff2" as="font" type="font/woff2" />
<link rel="icon" href="/favicon.png" />
<link rel="stylesheet" href="/style.css" /> <link rel="stylesheet" href="/style.css" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width" /> %svelte.head%
%sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body>
<div style="display: contents">%sveltekit.body%</div> <div id="svelte">%svelte.body%</div>
</body> </body>
</html> </html>

View File

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

View File

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

View File

@ -1,9 +1,8 @@
<script context="module"> <script context="module">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { formatDate } from './datefmt.js'; import { formatDate } from './datefmt.js';
import { makeSlug } from '$lib/utils.js'; import { makeSlug } from '$lib/slug.js';
import Toc from './Toc.svelte';
import Link from './Link.svelte'; import Link from './Link.svelte';
export { Link as a }; export { Link as a };
</script> </script>
@ -12,73 +11,14 @@
export let title, date; export let title, date;
export const description = ''; export const description = '';
export const draft = false; export const draft = false;
export let toc = null;
export let prev = null;
export let next = null;
</script> </script>
<style> <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 { .subtitle {
font-size: 0.9em; font-size: 0.9em;
font-style: italic; font-style: italic;
margin-top: -0.5rem; 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: 1rem;
font-size: 0.9rem;
color: var(--content-color-faded);
text-decoration: none;
transition: 150ms;
will-change: transform;
}
.footer a:hover {
text-decoration: underline;
transform: scale(1.15);
}
.footer svg {
width: 1.5em;
}
</style> </style>
<svelte:head> <svelte:head>
@ -86,40 +26,8 @@
<link rel="stylesheet" href="/prism-dracula.css" /> <link rel="stylesheet" href="/prism-dracula.css" />
</svelte:head> </svelte:head>
<div class="page"> <div id="post">
<div class="title"> <h1 id="{makeSlug(title)}">{title}</h1>
<h1 id="{makeSlug(title)}">{title}</h1> <p class="subtitle">{formatDate(date)}</p>
<p class="subtitle">{formatDate(date)}</p> <slot></slot>
</div>
<div class="left-gutter">
<Toc items={toc} />
</div>
<div class="post">
<slot></slot>
</div>
<hr>
<div class="footer">
{#if prev}
<a href="/{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" class="w-6 h-6">
<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}" 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" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
</svg>
</a>
{/if}
</div>
</div> </div>

View File

@ -1,4 +1,4 @@
<style> <style lang="scss">
/* always applicable */ /* always applicable */
:global(body) { :global(body) {
counter-reset: sidenote; counter-reset: sidenote;
@ -8,30 +8,30 @@
counter-increment: sidenote; counter-increment: sidenote;
color: #444; color: #444;
margin-left: 0.05rem; margin-left: 0.05rem;
}
.counter:after { &:after {
font-size: 0.75em; font-size: 0.75em;
position: relative; position: relative;
bottom: 0.3rem; bottom: 0.3rem;
color: #8c0606; color: #8c0606;
}
} }
.sidenote { .sidenote {
color: #555; color: #555;
font-size: 0.8rem; font-size: 0.8rem;
}
.sidenote:before { &:before {
content: var(--sidenote-index, counter(sidenote)) " "; content: counter(sidenote) " ";
/* absolute positioning puts it at the top-left corner of the sidenote, overlapping with the content /* 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) */ (because the sidenote is floated it counts as a positioned parent, I think) */
position: absolute; position: absolute;
/* translate moves it out to the left (and just a touch up to mimic the superscript efect) /* 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) */ -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); transform: translate(calc(-100% - 0.2rem), -0.15rem);
font-size: 0.75rem; font-size: 0.75rem;
color: #8c0606; color: #8c0606;
}
} }
.sidenote-toggle { .sidenote-toggle {
@ -46,28 +46,16 @@
.sidenote { .sidenote {
--gap: 2rem; --gap: 2rem;
--sidenote-width: min(16rem, calc(50vw - var(--gap) - 1rem - var(--content-width) / 2)); --sidenote-width: min(14rem, calc(50vw - var(--gap) - var(--content-width) / 2));
width: var(--sidenote-width); width: var(--sidenote-width);
hyphens: auto; hyphens: auto;
position: relative; position: relative;
float: right; float: right;
clear: 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; 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 { .nested.sidenote {
margin-right: 0; margin-right: 0;
margin-top: 0.7rem; margin-top: 0.7rem;
@ -119,13 +107,26 @@
font-size: 1.25rem; font-size: 1.25rem;
color: #8c0606; color: #8c0606;
cursor: pointer; cursor: pointer;
}
.dismiss:hover { &:hover {
transform: scale(1.1); transform: scale(1.1);
font-weight: 800; 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> </style>
<script context="module"> <script context="module">

View File

@ -1,143 +0,0 @@
<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

@ -0,0 +1,20 @@
<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

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

50
src/lib/slug.js Normal file
View File

@ -0,0 +1,50 @@
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);
}
})
}
}

View File

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

View File

@ -1,94 +0,0 @@
// 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('<', '&gt;')
.replaceAll('>', '&lt;');
}

View File

@ -1,90 +0,0 @@
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';
}

View File

@ -1,48 +0,0 @@
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});
}

View File

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

View File

@ -1,39 +0,0 @@
<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>

View File

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

View File

@ -1,11 +0,0 @@
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,
};
}

View File

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

24
src/routes/[slug].svelte Normal file
View File

@ -0,0 +1,24 @@
<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

@ -1,18 +0,0 @@
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

@ -1,10 +0,0 @@
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

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

View File

@ -0,0 +1,45 @@
<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,38 +0,0 @@
import { dev } from '$app/environment';
const posts = import.meta.globEager('./*.svx');
let postData = [];
for (const path in posts) {
// skip draft posts in production mode
if (!dev && posts[path].metadata.draft) {
continue;
}
// slice off the ./ and the .svx
const slug = path.slice(2, -4);
posts[path].metadata.slug = slug;
postData.push(posts[path].metadata);
}
let ids = new Set();
for (const postMeta of postData) {
if (postMeta.uuid === undefined) {
throw(`Missing UUID for post: ${postMeta.title}`);
}
if (ids.has(postMeta.uuid)) {
throw(`Duplicate UUID in post: ${postMeta.title}`);
}
ids.add(postMeta.uuid);
}
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 { postData };

View File

@ -2,7 +2,6 @@
title: Exposing Docker Containers to your LAN title: Exposing Docker Containers to your LAN
description: If, for some strange reason, you should want to do such a thing. description: If, for some strange reason, you should want to do such a thing.
date: 2022-03-21 date: 2022-03-21
uuid: 81715fb3-990e-487e-9662-fed7b7d02943
--- ---
<script> <script>
import Sidenote from '$lib/Sidenote.svelte'; import Sidenote from '$lib/Sidenote.svelte';

View File

@ -2,7 +2,6 @@
title: The Hitchiker's Guide to Mesh VPNs title: The Hitchiker's Guide to Mesh VPNs
description: The golden age of VPNery is upon us. description: The golden age of VPNery is upon us.
date: 2022-03-17 date: 2022-03-17
uuid: fc6930ef-979c-4851-bc5a-c0e1b1698061
--- ---
<script> <script>
import Sidenote from '$lib/Sidenote.svelte'; import Sidenote from '$lib/Sidenote.svelte';

View File

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

View File

@ -2,7 +2,6 @@
title: 'Languages: High and Low' title: 'Languages: High and Low'
description: How high is up? description: How high is up?
date: 2022-08-19 date: 2022-08-19
uuid: 89ae4194-7785-4fac-a841-8bcf5a5a3a2e
draft: true draft: true
--- ---

View File

@ -2,11 +2,12 @@
title: Sidenotes title: Sidenotes
description: An entirely-too-detailed dive into how I implemented sidenotes for this blog. description: An entirely-too-detailed dive into how I implemented sidenotes for this blog.
date: 2023-08-14 date: 2023-08-14
uuid: c514c46e-92f3-4078-a76b-e1dafd5f7e07
--- ---
<script> <script>
import Dropcap from '$lib/Dropcap.svelte';
import Sidenote from '$lib/Sidenote.svelte'; import Sidenote from '$lib/Sidenote.svelte';
import UnstyledSidenote from '$lib/UnstyledSidenote.svelte'; import UnstyledSidenote from '$lib/UnstyledSidenote.svelte';
import Frame from '$lib/projects/sidenotes/Frame.svelte';
</script> </script>
<style> <style>
@ -63,7 +64,7 @@ uuid: c514c46e-92f3-4078-a76b-e1dafd5f7e07
} }
</style> </style>
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 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>
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. 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

@ -2,7 +2,6 @@
title: Let's Design A Simpler SocketIO title: Let's Design A Simpler SocketIO
date: 2021-10-16 date: 2021-10-16
description: SocketIO is packed with features. But do we really need all of them all the time? description: SocketIO is packed with features. But do we really need all of them all the time?
uuid: 95cde7e7-9293-4fab-a0b4-fc6ab7da08c8
draft: true draft: true
--- ---

View File

@ -2,7 +2,6 @@
title: Sufficiently Advanced Technology Is Often Distinguishable From Magic title: Sufficiently Advanced Technology Is Often Distinguishable From Magic
description: I see what Arthur C. Clarke was getting at, but I don't think I agree. description: I see what Arthur C. Clarke was getting at, but I don't think I agree.
date: 2022-05-14 date: 2022-05-14
uuid: 84636766-eb78-4060-a98d-593d8d5b55c9
draft: true draft: true
--- ---
<script> <script>

View File

@ -2,14 +2,14 @@
title: Thoughts on Vue vs Svelte title: Thoughts on Vue vs Svelte
description: They're more similar than they are different, but they say the most bitter enemies are those who have the fewest differences. description: They're more similar than they are different, but they say the most bitter enemies are those who have the fewest differences.
date: 2023-06-29 date: 2023-06-29
uuid: 8280f0e0-6bf5-43a2-9eac-b8c2508cca29
--- ---
<script> <script>
import Dropcap from '$lib/Dropcap.svelte';
import Sidenote from '$lib/Sidenote.svelte'; import Sidenote from '$lib/Sidenote.svelte';
</script> </script>
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 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>
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: 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 * Single-file components with separate sections for markup, style, and logic

View File

@ -1,49 +0,0 @@
import { tag, text, serialize } from '$lib/xml.js';
import { postData } from '../_posts/all.js';
export function GET() {
return new Response(renderFeed(), {
headers: {'Content-Type': 'text/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', {href: 'https://blog.jfmonty2.com/'});
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 entry = feed.addTag('entry');
entry.addTag('title', {}, [text(post.title)]);
entry.addTag('link', {rel: 'alternate', href: `https://blog.jfmonty2.com/${post.slug}`});
entry.addTag('id', {}, [text(post.uuid)]);
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();
}

18
src/routes/index.svelte Normal file
View File

@ -0,0 +1,18 @@
<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

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

28
src/routes/posts.js Normal file
View File

@ -0,0 +1,28 @@
import { dev } from '$app/env';
const posts = import.meta.globEager('./_posts/*.svx');
export 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)
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}
};
}

View File

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

View File

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

View File

@ -1,24 +1,24 @@
import staticAdapter from '@sveltejs/adapter-static';
import { mdsvex } from 'mdsvex'; import { mdsvex } from 'mdsvex';
import staticAdapter from '@sveltejs/adapter-static';
import svp from 'svelte-preprocess';
import slug from './src/lib/slug.js';
import { localRemark } from './src/plugins/remark.js';
import { localRehype } from './src/plugins/rehype.js';
/** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
extensions: ['.svelte', '.svx'], extensions: ['.svelte', '.svx'],
preprocess: [ preprocess: [
mdsvex({ mdsvex({
layout: './src/lib/Post.svelte', layout: './src/lib/Post.svelte',
remarkPlugins: [localRemark], rehypePlugins: [slug],
rehypePlugins: [localRehype],
}), }),
svp.scss(),
], ],
kit: { kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // hydrate the <div id="svelte"> element in src/app.html
// 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(), adapter: staticAdapter(),
prerender: {
default: true,
},
} }
}; };

281
tmp/crane.svg Normal file
View File

@ -0,0 +1,281 @@
<?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>

After

Width:  |  Height:  |  Size: 17 KiB

99
tmp/index.html Normal file
View File

@ -0,0 +1,99 @@
<!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>

View File

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