--- /dev/null
+# https://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+tab_width = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+max_line_length = 80
--- /dev/null
+# ardislu.dev
+
+This is the frontend for [my personal blog](https://ardislu.dev).
+
+Also check out my [main website](https://ardis.lu)!
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33c.85 0 1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2z" fill="currentColor"/><rect x="0" y="0" width="24" height="24" fill="rgba(0, 0, 0, 0)" /></svg>
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="Ardis Lu's personal blog." />
+ <link rel="icon" href="favicon.ico" type="image/x-icon" />
+ <link href="style.css" rel="stylesheet" />
+ <title>ardislu.dev</title>
+ </head>
+
+ <body>
+ <header>
+ <h1>ardislu.dev</h1>
+ <a href="https://github.com/ardislu/">
+ <img src="github.svg" width="24" height="24" alt="GitHub" />
+ </a>
+ </header>
+ <main></main>
+ <script src="script.js"></script>
+ </body>
+</html>
--- /dev/null
+const main = document.getElementsByTagName("main")[0];
+const initialColumns = Math.max(1, Math.floor(main.clientWidth / 240) - 1);
+
+/* Generic element builders */
+const createColumnElement = () => {
+ const el = document.createElement("div");
+ el.classList.add("column");
+ return el;
+};
+
+const createSkeletonElement = () => {
+ const el = document.createElement("article");
+ el.innerHTML = '<div class="skeleton-line"></div>'.repeat(6);
+ return el;
+};
+
+const createArticleElement = (article) => {
+ const el = document.createElement("article");
+ el.innerHTML = `<h2>${article.title}</h2><p>${article.body}</p><p><a href="#">Read More</a></p>`;
+ return el;
+};
+
+/* Inject placeholder skeleton loaders */
+for (let i = 0; i < initialColumns; i++) {
+ const col = createColumnElement();
+ for (let j = 0; j < 12; j++) {
+ const a = createSkeletonElement();
+ col.insertAdjacentElement("beforeend", a);
+ }
+ main.insertAdjacentElement("beforeend", col);
+}
+
+/* Callback to inject content from API */
+const inject = (resp) => {
+ main.innerHTML = "";
+ const chunkSize = Math.ceil(resp.length / initialColumns);
+ for (let i = 0; i < initialColumns; i++) {
+ const col = createColumnElement();
+ const chunk = resp.splice(0, chunkSize);
+ for (let a of chunk) {
+ col.insertAdjacentElement("beforeend", createArticleElement(a));
+ }
+ main.insertAdjacentElement("beforeend", col);
+ }
+};
+
+fetch("https://jsonplaceholder.typicode.com/posts").then((httpResponse) =>
+ httpResponse.json().then((jsonResponse) => inject(jsonResponse))
+);
--- /dev/null
+body {
+ font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto,
+ "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", Helvetica, Arial,
+ sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ color: #454545;
+ background: #eee;
+}
+
+header {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+main {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ grid-gap: 1rem;
+ justify-items: center;
+ margin: 0 auto;
+ max-width: 1440px;
+}
+
+.column {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ width: 100%;
+}
+
+article {
+ margin-bottom: 1rem;
+ padding: 0 1rem;
+ width: 100%;
+ border: solid rgba(0, 0, 0, 0.1);
+ border-radius: 1rem;
+ box-shadow: 0.25rem 0.25rem rgba(0, 0, 0, 0.1);
+}
+
+@keyframes shimmer {
+ 100% {
+ background: #d6d6d6;
+ }
+}
+
+.skeleton-line {
+ margin: 1rem;
+ height: 1rem;
+ animation: shimmer 1s alternate infinite;
+ background: #959595;
+}