blog!
This commit is contained in:
parent
3bae312ec4
commit
1aef9cc9f9
10
index.html
10
index.html
|
@ -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>
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"watch": ["vite.config.ts","src", "public"],
|
||||
"ext": "js,ts,html,css,md",
|
||||
"exec": "npm run preview"
|
||||
}
|
13
package.json
13
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
}
|
|
@ -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) {
|
|
@ -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
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue