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 = ` +
+ +

${data.title!}

+

${new Date(data.date!).toDateString()}

+

${data.desc!}

+
+
+ `; + + 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); + }, + }, + ], + }, + }, +});