This commit is contained in:
august kline 2024-09-30 19:16:48 -04:00
parent 3bae312ec4
commit 1aef9cc9f9
8 changed files with 445 additions and 60 deletions

View File

@ -10,12 +10,13 @@
</head>
<body>
<div id="app">
<div class="info">
<main>
<h1>hi! i'm august. or kline. </h1>
<p>i like to build systems. lately i've been working on
<p>lately i've been working on
<a href="https://git.augustkline.com/august/george" target="_blank" rel="noreferer">george</a>.
here's my
<a href="/blog/">blog</a>
and my
<a href="https://git.augustkline.com/august" target="_blank" rel="noreferer">git</a>
and my
<a href="https://kline.cohost.org" target="_blank" rel="noreferer">cohost</a>
@ -25,10 +26,9 @@
<a href="/augustklineResume.pdf" target="_blank" rel="noreferer">resume</a>
too!)
</p>
</div>
</main>
<div id="background">
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>

5
nodemon.json Normal file
View File

@ -0,0 +1,5 @@
{
"watch": ["vite.config.ts","src", "public"],
"ext": "js,ts,html,css,md",
"exec": "npm run preview"
}

View File

@ -6,10 +6,19 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"preview": "npm run build && vite preview"
},
"devDependencies": {
"@types/node": "^22.7.4",
"@types/xml": "^1.0.11",
"feed": "^4.2.2",
"highlight.js": "^11.10.0",
"marked": "^14.1.2",
"typescript": "^5.2.2",
"vite": "^5.2.0"
"vite": "^5.2.0",
"gray-matter": "^4.0.3",
"http-server": "^14.1.1",
"nodemon": "^3.1.7",
"xml": "^1.0.1"
}
}

125
public/github.css Normal file
View File

@ -0,0 +1,125 @@
/*!
Theme: GitHub
Description: Light theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-light
Current colors taken from GitHub's CSS
*/
.hljs {
color: #24292e;
background: #ffffff;
}
.hljs-doctag,
.hljs-keyword,
.hljs-meta .hljs-keyword,
.hljs-template-tag,
.hljs-template-variable,
.hljs-type,
.hljs-variable.language_ {
/* prettylights-syntax-keyword */
color: #d73a49;
}
.hljs-title,
.hljs-title.class_,
.hljs-title.class_.inherited__,
.hljs-title.function_ {
/* prettylights-syntax-entity */
color: #6f42c1;
}
.hljs-attr,
.hljs-attribute,
.hljs-literal,
.hljs-meta,
.hljs-number,
.hljs-operator,
.hljs-variable,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-id {
/* prettylights-syntax-constant */
color: #005cc5;
}
.hljs-regexp,
.hljs-string,
.hljs-meta .hljs-string {
/* prettylights-syntax-string */
color: #032f62;
}
.hljs-built_in,
.hljs-symbol {
/* prettylights-syntax-variable */
color: #e36209;
}
.hljs-comment,
.hljs-code,
.hljs-formula {
/* prettylights-syntax-comment */
color: #6a737d;
}
.hljs-name,
.hljs-quote,
.hljs-selector-tag,
.hljs-selector-pseudo {
/* prettylights-syntax-entity-tag */
color: #22863a;
}
.hljs-subst {
/* prettylights-syntax-storage-modifier-import */
color: #24292e;
}
.hljs-section {
/* prettylights-syntax-markup-heading */
color: #005cc5;
font-weight: bold;
}
.hljs-bullet {
/* prettylights-syntax-markup-list */
color: #735c0f;
}
.hljs-emphasis {
/* prettylights-syntax-markup-italic */
color: #24292e;
font-style: italic;
}
.hljs-strong {
/* prettylights-syntax-markup-bold */
color: #24292e;
font-weight: bold;
}
.hljs-addition {
/* prettylights-syntax-markup-inserted */
color: #22863a;
background-color: #f0fff4;
}
.hljs-deletion {
/* prettylights-syntax-markup-deleted */
color: #b31d28;
background-color: #ffeef0;
}
.hljs-char.escape_,
.hljs-link,
.hljs-params,
.hljs-property,
.hljs-punctuation,
.hljs-tag {
/* purposely ignored */
}

View File

@ -11,60 +11,101 @@
--link-color: var(--text-color);
--link-shadow-color: rgba(0, 0, 0, 0.5);
--border-color: rgba(200, 200, 200, 0.5);
--text-bg-color: rgba(255, 255, 255, 0.9);
--text-bg-color: rgba(255, 255, 255, 0.95);
--text-border-shadow-color: rgba(255, 255, 255, 0.7);
--text-shadow-color: rgba(0, 0, 0, 0.1);
--img-bg-color: rgba(50, 50, 50, 0.2);
--img-border-color: rgba(255, 255, 255, 0.8);
--font-base-size: 1rem;
}
body,
html {
inline-size: 100%;
block-size: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#app {
inline-size: 100%;
align-items: center;
block-size: 100%;
display: flex;
flex-direction: column;
inline-size: 100%;
justify-content: center;
margin: 0;
overflow: hidden;
padding: 0;
position: relative;
align-items: center;
justify-content: center;
flex-direction: column;
overflow: hidden;
}
h1 {
font-size: 1.5rem;
h1,
h2,
h3 {
margin-block: .5rem;
}
h1 {
font-size: calc(var(--font-base-size) * 2);
}
h2 {
font-size: calc(var(--font-base-size) * 1.8);
}
h3 {
font-size: calc(var(--font-base-size) * 1.6);
}
p {
font-size: 1.5rem;
font-size: calc(var(--font-base-size) * 1.5);
margin: 0;
text-align: justify;
text-align-last: center;
inline-size: 100%;
}
a {
color: var(--link-color);
text-decoration: none;
transition: .2s text-shadow, .2s transform;
display: inline-block;
font-weight: 600;
}
a:hover {
a {
color: var(--link-color);
transition: .2s text-shadow, .2s transform;
display: inline-block;
font-weight: 600;
}
a {
transition: .2s text-shadow, .2s box-shadow,
.2s transform;
&:has(> p) {
&:hover {
box-shadow: 0rem 0.2rem 1rem var(--link-shadow-color);
transform: translateY(-7%);
}
color: var(--link-color);
display: inline-block;
border-radius: 1rem;
text-decoration: none;
}
&:not(:has(> p)) {
&:hover {
text-shadow: 0rem 0.2rem 1rem var(--link-shadow-color);
transform: translateY(-7%) scale(1.01);
}
}
}
#background {
transform-style: preserve-3d;
transform: rotateX(var(--rotate-x)) rotateY(var(--rotate-y));
inline-size: 100%;
block-size: 100%;
z-index: -1;
position: absolute;
&[loaded] img {
opacity: 1;
}
img {
transform: translate(-50%, -50%) translateZ(var(--translate-z));
@ -80,37 +121,42 @@ img {
-0.05rem 0.05rem 0px var(--img-bg-color),
0 0 1rem rgba(0, 0, 0, 0.3);
}
#background {
transform-style: preserve-3d;
transform: rotateX(var(--rotate-x)) rotateY(var(--rotate-y));
inline-size: 100%;
block-size: 100%;
z-index: -1;
position: absolute;
&[loaded] img {
opacity: 1;
}
}
.info {
pre {
padding: 0.5rem 1rem;
border-radius: 1rem;
box-shadow: 0.05rem 0.05rem 0px var(--img-bg-color),
-0.05rem -0.05rem 0px var(--img-bg-color),
0.05rem -0.05rem 0px var(--img-bg-color),
-0.05rem 0.05rem 0px var(--img-bg-color),
0 0 1rem rgba(0, 0, 0, 0.3);
}
.blog {}
main {
overflow-inline: auto;
display: flex;
flex-direction: column;
align-items: center;
background: var(--text-bg-color);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 0.01rem solid var(--border-color);
margin: 1px;
padding: .8rem 1.6rem;
inline-size: clamp(20rem, 30%, 40rem);
max-inline-size: clamp(20rem, 50%, 40rem);
border-radius: 1rem;
box-shadow: 0.05rem 0.05rem 0px var(--text-border-shadow-color),
-0.05rem -0.05rem 0px var(--text-border-shadow-color),
0.05rem -0.05rem 0px var(--text-border-shadow-color),
-0.05rem 0.05rem 0px var(--text-border-shadow-color),
0rem 0rem 2rem var(--text-shadow-color);
img {
inline-size: 100%;
border-radius: 1rem;
}
}
@media (prefers-reduced-motion: reduce) {

13
src/blog/init.md Normal file
View File

@ -0,0 +1,13 @@
---
title: hello word
date: 2024-9-30
desc: the first post on my blog! check back in soon cause this is very janky rn lol
---
# hi!
i just wanted to get something up and running before cohost shuts down
this will look prettier soon i was just rushing!
you can subscribe with [rss](/feed.xml) and eventually i think i will add automated instagram posts whenever there's a new update, but that's for later

View File

@ -191,7 +191,8 @@ window.addEventListener("load", () => {
const dpi = window.devicePixelRatio;
const moveImages = (t: number) => {
const images = document.body.getElementsByTagName("img");
const background = document.getElementById("background")!;
const images = background.getElementsByTagName("img");
for (let i = 0; i < images.length; i++) {
const img = images[i];
let top = toDomPrecision(

186
vite.config.ts Normal file
View File

@ -0,0 +1,186 @@
import { defineConfig } from "vite";
import fs from "fs";
import path from "path";
import { marked, Token, Tokens } from "marked";
import xml from "xml";
import matter from "gray-matter";
import highlight from "highlight.js";
const blogDir = path.resolve(__dirname, "src/blog");
const outputDir = path.resolve(__dirname, "dist/blog");
const rssFeedPath = path.resolve(__dirname, "dist/feed.xml");
const hostname = "https://augustkline.com";
const blogUrl = `${hostname}/blog`;
interface Articles {
title: string;
link: string;
description: string;
pubDate: string;
}
const renderer = new marked.Renderer();
renderer.code = ({ text, lang }: Tokens.Code) => {
const validLang = highlight.getLanguage(lang!) ? lang! : "plaintext";
const highlightedCode = highlight.highlight(text!, { language: validLang });
return `<pre><code class="language-${validLang}">${highlightedCode.value}</code></pre>`;
};
marked.setOptions({
renderer,
gfm: true,
breaks: true,
});
function generateBlogHtml() {
fs.mkdirSync(outputDir, { recursive: true });
const articles: Articles[] = [];
let blurbs: { date: string; content: string }[] = [];
fs.readdirSync(blogDir).forEach((file) => {
if (file.endsWith(".md")) {
const filePath = path.join(blogDir, file);
const str = fs.readFileSync(filePath, "utf-8");
let { data, content } = matter(str);
const slug = `${data.date!}-${data.title!.replace(/ /g, "-")}`;
const html_content = marked.parse(content);
const blogPostsPath = path.join(outputDir, `${slug}.html`);
const postHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${data.title!}</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<link rel="stylesheet" type="text/css" href="/github.css">
</head>
<body>
<main>
${html_content}
<a href="/">Back to Home</a>
</main>
<div id="background">
</div>
</body>
<script type="module" src="/main.js"></script>
</html>
`;
const link = `/blog/${slug}.html`;
const blurbHtml = `
<div>
<a href="${link}">
<h2>${data.title!}</h2>
<p>${new Date(data.date!).toDateString()}</p>
<p>${data.desc!}</p>
</a>
</div>
`;
blurbs.push({ date: data.date!, content: blurbHtml });
fs.writeFileSync(blogPostsPath, postHtml);
// Store article data for RSS
articles.push({
title: data.title!,
link: link,
description: data.desc!,
pubDate: data.date!,
});
}
});
const blogIndexPath = path.join(outputDir, "index.html");
blurbs.sort((a, b) => {
return new Date(b.date).getTime() - new Date(a.date).getTime();
});
let blurbsHtml: string = "";
for (let blurb in blurbs) {
blurbsHtml = blurbsHtml.concat(blurbs[blurb].content);
}
const blogIndexHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>august kline's blog</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<link rel="stylesheet" type="text/css" href="/github.css">
</head>
<body>
<main>
<h1>ʕ··ʔ- august kline's blog </h1>
${blurbsHtml}
</main>
<div id="background">
</div>
</body>
<script type="module" src="/main.js"></script>
</html>
`;
fs.writeFileSync(blogIndexPath, blogIndexHtml);
return articles;
}
// Function to generate RSS feed
function generateRssFeed(articles: Articles[]) {
const feedItems = articles.map((article) => ({
item: [
{ title: article.title },
{ link: article.link },
{ description: article.description },
{ pubDate: article.pubDate },
],
}));
const rss = {
rss: [
{
channel: [
{ title: "ʕ·ᴥ·ʔ-☆ august kline's blog ☆" },
{ link: `${blogUrl}` }, // Update with your domain
{
description:
"whatever's on my mind, short essays, tech stuff, etc :)",
},
...feedItems,
],
},
],
};
const rssXml = xml(rss, { declaration: true });
fs.writeFileSync(rssFeedPath, rssXml);
}
// Export Vite config
export default defineConfig({
build: {
rollupOptions: {
output: {
entryFileNames: `main.js`,
},
plugins: [
{
name: "generate-blog-html-and-rss",
writeBundle() {
// parses blog markdown, writes the blog endpoints and returns array of article data
const articles = generateBlogHtml();
generateRssFeed(articles);
},
},
],
},
},
});