diff --git a/index.html b/index.html
index 1dd06d5..f83b370 100644
--- a/index.html
+++ b/index.html
@@ -10,24 +10,24 @@
-
-
-
hi! i'm august. or kline.
-
i like to build systems. lately i've been working on
- george.
- here's my
- git
- and my
- cohost
- and my
- email
- (and my
- resume
- too!)
-
-
-
-
+
+ hi! i'm august. or kline.
+ lately i've been working on
+ george.
+ here's my
+ blog
+ and my
+ git
+ and my
+ cohost
+ and my
+ email
+ (and my
+ resume
+ too!)
+
+
+
diff --git a/nodemon.json b/nodemon.json
new file mode 100644
index 0000000..b794599
--- /dev/null
+++ b/nodemon.json
@@ -0,0 +1,5 @@
+{
+ "watch": ["vite.config.ts","src", "public"],
+ "ext": "js,ts,html,css,md",
+ "exec": "npm run preview"
+}
diff --git a/package.json b/package.json
index 11c8001..8164b3b 100644
--- a/package.json
+++ b/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"
}
}
diff --git a/public/github.css b/public/github.css
new file mode 100644
index 0000000..858e99e
--- /dev/null
+++ b/public/github.css
@@ -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 */
+}
diff --git a/style.css b/public/style.css
similarity index 58%
rename from style.css
rename to public/style.css
index 1e23f11..cd34da7 100644
--- a/style.css
+++ b/public/style.css
@@ -11,74 +11,88 @@
--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 {
- text-shadow: 0rem 0.2rem 1rem var(--link-shadow-color);
- transform: translateY(-7%) scale(1.01);
+a {
+ color: var(--link-color);
+ transition: .2s text-shadow, .2s transform;
+ display: inline-block;
+ font-weight: 600;
}
-img {
- transform: translate(-50%, -50%) translateZ(var(--translate-z));
- transition: opacity 0.3s;
- opacity: 0;
- position: absolute;
- z-index: -1;
- border: 0.01rem solid var(--img-border-color);
- 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);
+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 {
@@ -92,25 +106,57 @@ img {
&[loaded] img {
opacity: 1;
}
+
+ img {
+ transform: translate(-50%, -50%) translateZ(var(--translate-z));
+ transition: opacity 0.3s;
+ opacity: 0;
+ position: absolute;
+ z-index: -1;
+ border: 0.01rem solid var(--img-border-color);
+ 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);
+ }
}
-.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) {
diff --git a/src/blog/init.md b/src/blog/init.md
new file mode 100644
index 0000000..a48c52a
--- /dev/null
+++ b/src/blog/init.md
@@ -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
diff --git a/src/main.ts b/src/main.ts
index d81c8d3..87b707e 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -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(
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..e5bd5d0
--- /dev/null
+++ b/vite.config.ts
@@ -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 `
${highlightedCode.value}
`;
+};
+
+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 = `
+
+
+
+
+
+
${data.title!}
+
+
+
+
+
+ ${html_content}
+ Back to Home
+
+
+
+
+
+
+ `;
+
+ const link = `/blog/${slug}.html`;
+ const blurbHtml = `
+
+ `;
+
+ 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 = `
+
+
+
+
+
+
august kline's blog
+
+
+
+
+
+ ʕ·ᴥ·ʔ-☆ august kline's blog ☆
+ ${blurbsHtml}
+
+
+
+
+
+
+ `;
+ 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);
+ },
+ },
+ ],
+ },
+ },
+});