mirror of
https://github.com/mikkelsvartveit/astro-personal-website.git
synced 2025-12-22 19:22:37 +00:00
Merge branch 'blog'
This commit is contained in:
commit
60ef413989
16 changed files with 239 additions and 104 deletions
BIN
src/assets/images/lorempicsum.jpeg
Normal file
BIN
src/assets/images/lorempicsum.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
src/assets/images/rocket.png
Normal file
BIN
src/assets/images/rocket.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
|
|
@ -8,7 +8,7 @@ const { href, class: className, ...attributes } = Astro.props;
|
|||
---
|
||||
|
||||
<a
|
||||
class={`text-teal-600 duration-100 hover:text-teal-500 hover:underline ${
|
||||
class={`text-emerald-700 duration-100 underline underline-offset-2 hover:text-emerald-500 ${
|
||||
className ?? ""
|
||||
}`}
|
||||
{href}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@
|
|||
$: isScrolled = scrollPosition > 0;
|
||||
|
||||
const navbarContent = [
|
||||
{ name: "📝 Articles", href: "/articles" },
|
||||
{ name: "👨💻 Projects", href: "/programming" },
|
||||
{ name: "📷 Photography", href: "/photography" },
|
||||
// { name: "📝 Articles", href: "/articles" },
|
||||
];
|
||||
|
||||
let collapsed = true;
|
||||
|
|
|
|||
68
src/content/blog/astro-welcome.md
Normal file
68
src/content/blog/astro-welcome.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
intro: "How I built this website, why I picked the Astro framework, and the reason Tailwind is the best way to write CSS in 2023!"
|
||||
image: "@assets/images/rocket.png"
|
||||
date: 2023-10-30
|
||||
---
|
||||
|
||||
# Welcome to My New Personal Website!
|
||||
|
||||
Thanks for checking out my brand new portfolio website! In this write-up, I’ll walk through the purpose of this website, explain the frameworks and tools I used to build and deploy it, as well as some challenges I discovered along the way. Let’s get started!
|
||||
|
||||
## Why I built this site
|
||||
|
||||
I’ve wanted to build a new website for a while, and finally found the time and motivation to do so. When developing this site I had a few goals in mind:
|
||||
|
||||
1. Having an online portfolio where I can showcase my stuff, namely programming projects and photos I’ve taken.
|
||||
2. Being able to share ideas and knowlegde through the occacional blog post (like this one).
|
||||
3. Improving my online presence on a platform that I fully control.
|
||||
4. Having a platform where I can play with and experiment with new web technologies that I find interesting.
|
||||
|
||||
With that out of the way, let’s run through the technologies that power [mikkelsvartveit.com](https://mikkelsvartveit.com)
|
||||
|
||||
## Astro
|
||||
|
||||
The main tool that this website was built with is [Astro](https://astro.build). Astro takes a different approach than other JavaScript frameworks. Where Next.js, SvelteKit and the likes are mainly targeting web _applications_, Astro is really meant for writing websites where content is the focus. In a way, where other frameworks tend to feel centered around JavaScript, Astro is centered around HTML. Astro has a few tricks up its sleeve that makes it a great fit for a website like this.
|
||||
|
||||
### First-class Markdown integration
|
||||
|
||||
I wrote the first version of this website in SvelteKit. I love Svelte and SvelteKit. However, it turns out that using Markdown with SvelteKit is not ideal. The nice thing about Markdown is that it makes it super easy for me to publish content on the website. I can write an article in my note-taking app, export it as Markdown, and place it right into the Git repository. While I did eventually get Markdown working with a third-party library in SvelteKit, it felt hacky and had a few annoying quirks. Astro, on the other hand, was built with Markdown in mind from day one. Just place the Markdown files in a special `content/` folder, and you can easily map each file to a page route. Astro even makes sure that the frontmatter (a special section on top of the Markdown file where you can define things like dates and tags) follows the format you intend, and throws a type error whenever you make a mistake.
|
||||
|
||||
### Automatic image optimization
|
||||
|
||||
I have a lot of images around this website. In SvelteKit, I wrote a Node.js script that took all my huge photos, resized them in two different sizes (one for thumbnails and one for full-screen views), and placed them in another directory. This script ran every time I did the `npm run build` process. While this did work just fine, Astro made it so much more intuitive with the built-in `<Image />` component. Just replace the regular `<img>` tag with this component, specify the resolution you want, and Astro automatically generates optimized versions of the images at build-time. You can even make it generate multiple file types and resolutions for each image, allowing the browser to choose the best version of the image itself. Nice!
|
||||
|
||||
### Bring your own UI framework
|
||||
|
||||
Another _really_ cool feature of Astro is the ability to use components from pretty much any modern JavaScript framework you can think of. You can use React, Vue or Svelte within your Astro pages, and even mix components from different frameworks on the same page! This is not only impressive from a technical standpoint, but also super useful. Unlike most web frameworks, Astro ships zero JavaScript as default. This is great for performance and accessibility, but once in a while it's nice to write components in something like React when you really need the interactiveness.
|
||||
|
||||
I utilized this feature for the navigation bar on the top of this site. It has a box shadow that shows and hides dynamically, and on mobile browsers it shows a menu button that expands a shelf of navigation links. Sure, I could have done this with vanilla JS, but I felt that using a Svelte component would be simpler and more maintainable. Besides, I already had this component ready from the old SvelteKit version of the website! I simply copied this file over, and it just worked right away, which was kind of a mind-blowing moment.
|
||||
|
||||
### View Transitions
|
||||
|
||||
While the new View Transitions API is only supported in Chrome and other Chromium-based browsers for now, it is undeniably very cool. Astro utilizes this API in a really frictionless way. With just _one line_ of code, Astro will enable a 3KB large Javascript router that provides really sleek transitions between all your pages. And with a few more lines, you can achieve even more impressive transitions with elements persisting across pages. I’ve tried doing this on my photography page to medium success, yet the best demo I’ve seen for this is this [Spotify clone](https://spotify-astro-transitions.vercel.app) made by Igor M. Penaque. Go check it out!
|
||||
|
||||
## Tailwind CSS
|
||||
|
||||
I’ve heard people talking about Tailwind for years, but I never really gave it any thought until quite recently. You see, Tailwind is one of those things that seem _really_ bad in theory. What it does is basically to provide you with a bunch of HTML classes for almost every CSS property that exists. The idea is that instead of writing CSS, you just throw a bunch of these classes into each HTML element and call it a day. Sounds terrible, right? Well, I thought so too, until I heard a coworker say something that really hit home with me. I’m paraphrasing, but he said something along the lines of “styling with Tailwind is so fast that I no longer bother with Figma”. I really don’t like Figma at all, as I just get the feeling of having to implement the same styles twice, first in Figma and then in CSS, so this point naturally made me curious.
|
||||
|
||||
I decided to actually give Tailwind a try, and now I would honestly call Tailwind my favorite web development tool ever. It solves pretty much all the things I didn’t know I disliked about CSS:
|
||||
|
||||
- There is no need to come up with HTML classes yourself anymore. This is a surprisingly delightful feature. Coming up with good class names selectors is hard, especially on a project where you collaborate with people that might have a different coding style than you. And all those hours wasted figuring out which CSS selectors gets the highest priority? Tailwind solves it. Your Tailwind classes determine how your element is styled, and there’s no need to think about anything more than that.
|
||||
- It reduces decision fatigue. As an overly perfectionist developer, I used to find myself making micro-adjustments to my styles that no one but myself would ever notice. I would frequently think “this color should probably be 2% darker”, or “this `div` for sure deserves one more pixel of bottom padding”. Tailwind mitigates this by providing a perfectly balanced style system, where the possible values are spaced apart in a way that limits your choices _just_ enough to reduce overthinking, while giving you the granularity you need.
|
||||
- Speaking of colors: instead of spending hours in a color picker trying to find the perfect hex value, Tailwind provides 22 base colors with 10 shades for each of them referenced in the format of `blue-400` and `amber-800`. This makes it super easy to keep a consistent color scheme but use different shades for stuff like hover effects. This color selection is so nice that in project where I don’t have Tailwind, I actually [reference the Tailwind colors from their docs](https://tailwindcss.com/docs/customizing-colors).
|
||||
- Each style is tightly coupled to a single element. This is great for a couple of reasons. First, if you decide to delete some HTML or JSX code down the line, you can be sure that the styling gets deleted with it. A lot of large projects have leftovers in the CSS file because the developers didn’t realize that style is not in use anymore. On the flip side, you never have to worry about breaking something else when changing a style in Tailwind, because the style is only in use for that one element in the first place.
|
||||
- And, like my coworker suggested, building websites with Tailwind is _so_ fast. Not having to constantly context switch between HTML and CSS files is a relief I didn’t realize I needed.
|
||||
|
||||
However, my favorite feature of Tailwind is what it is not: a component library. Contrary to many developers I know, I actually like CSS and the flexibility it gives me, and for this reason I am not usually a fan of component libraries. These tend to lock the developer into pre-established styles, and once you try to move outside these constraints, things start to feel hacky really fast. Tailwind, though, is not a component library. It is CSS, just packaged in a better way. Yes, the HTML gets ugly (and that is probably the strongest criticism I have against Tailwind), but this is a tradeoff I’m very willing to make at this point.
|
||||
|
||||
## Cloudflare Pages
|
||||
|
||||
The final piece of the puzzle is my hosting platform, Cloudflare Pages. Their free tier is unbelievably good. For $0, they give you unlimited sites, unlimited requests (including commercial use!), unlimited bandwidth, 500 builds per month, and automatic deployments every time you push to any branch in your GitHub repository.
|
||||
|
||||
In contrast to GitHub Pages, Cloudflare’s edge runtime (called Workers) allow you to easily enable server-side rendering features for frameworks like Next.js, Remix, SvelteKit, and Astro. I don’t use SSR for this site (it is statically rendered at build time), but I am for [Snipcast.io](https://snipcast.io). This is a SvelteKit app with some static routes, some server-rendered routes, and a bunch of API endpoints with JWT authentication and everything. Cloudflare Pages handles it all really well and was dead easy to set up.
|
||||
|
||||
Oh, and it’s also straightforward to connect your custom domain. You can even buy a domain through Cloudflare (which, by the way, has the cheapest prices of any registrar), in which case they care of all the DNS stuff for you when you add it to your Pages project.
|
||||
|
||||
## Conclusion
|
||||
|
||||
I’m very happy with my choice of tools for this site. Astro, Svelte, Tailwind, and Cloudflare Pages give me complexity when I want, but gets out of my way when I don’t. I get the feeling that with this current setup, I can build pretty much whatever I want, scale it to almost any amount of traffic, while still being able to quickly iterate and add new content. Let’s cross our fingers that the site lasts for at least a few months before a new Javascript framework launches and I get tempted to rewrite again :D
|
||||
|
|
@ -14,6 +14,17 @@ const programmingCollection = defineCollection({
|
|||
}),
|
||||
});
|
||||
|
||||
const blogCollection = defineCollection({
|
||||
type: "content",
|
||||
schema: ({ image }) =>
|
||||
z.object({
|
||||
image: image(),
|
||||
intro: z.string(),
|
||||
date: z.date(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
programming: programmingCollection,
|
||||
blog: blogCollection,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@ const { title, noStickyNavbar } = Astro.props;
|
|||
</main>
|
||||
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;600&family=Source+Serif+Pro:wght@300;400;600&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;600&family=Source+Code+Pro&family=Source+Serif+Pro:wght@300;400;600&display=swap");
|
||||
</style>
|
||||
</RootLayout>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="mx-auto max-w-5xl px-3 py-12 sm:px-6">
|
||||
<div class="mx-auto max-w-5xl px-4 py-12 sm:px-6">
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
---
|
||||
import ContainerLayout from "./ContainerLayout.astro";
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import TextContentLayout from "./TextContentLayout.astro";
|
||||
import type { MarkdownLayoutProps } from "astro";
|
||||
|
||||
type Props = MarkdownLayoutProps<{
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
technologies: string;
|
||||
website: string;
|
||||
repository?: string;
|
||||
date: string;
|
||||
}>;
|
||||
|
||||
const { title, website, repository, technologies, date } =
|
||||
Astro.props.frontmatter;
|
||||
---
|
||||
|
||||
<BaseLayout {title}>
|
||||
<ContainerLayout>
|
||||
<TextContentLayout>
|
||||
<h1
|
||||
class="mb-4 text-center font-serif text-4xl font-light tracking-wide text-gray-600 underline decoration-yellow-400 decoration-2 underline-offset-8"
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<p class="mx-auto mb-4 text-center text-sm text-gray-500">
|
||||
{
|
||||
new Date(date).toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
</p>
|
||||
|
||||
<div class="mb-8 flex flex-wrap justify-center">
|
||||
{
|
||||
technologies
|
||||
.split(",")
|
||||
.map((technology) => (
|
||||
<span class="m-1 rounded-full bg-gray-200 px-3 py-0.5 text-sm font-semibold text-gray-600">
|
||||
{technology}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mb-6 flex flex-col items-center justify-center sm:flex-row">
|
||||
{
|
||||
website && (
|
||||
<a
|
||||
href={website}
|
||||
target="_blank"
|
||||
class="mx-2 mb-2 block rounded-lg border border-gray-200 bg-white px-4 py-3 font-semibold tracking-wide text-gray-600 shadow duration-100 hover:bg-gray-50"
|
||||
>
|
||||
🖥️ Visit website
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
repository && (
|
||||
<a
|
||||
href={repository}
|
||||
target="_blank"
|
||||
class="mx-2 mb-2 block rounded-lg border border-gray-200 bg-white px-4 py-3 font-semibold tracking-wide text-gray-600 shadow duration-100 hover:bg-gray-50"
|
||||
>
|
||||
📦 Code on GitHub
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
class="prose prose-lg max-w-none font-serif prose-headings:text-gray-600 prose-h2:font-light prose-a:text-teal-600 prose-a:no-underline prose-a:duration-100 hover:prose-a:text-teal-500 hover:prose-a:underline before:prose-code:content-[''] after:prose-code:content-['']"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</TextContentLayout>
|
||||
</ContainerLayout>
|
||||
</BaseLayout>
|
||||
5
src/layouts/ProseLayout.astro
Normal file
5
src/layouts/ProseLayout.astro
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<div
|
||||
class="prose prose-lg max-w-none font-serif prose-headings:text-gray-600 prose-h1:font-light prose-h1:leading-snug prose-h1:underline prose-h1:decoration-yellow-400 prose-h1:decoration-2 prose-h1:underline-offset-8 prose-p:text-gray-700 prose-a:text-emerald-700 prose-a:underline-offset-2 prose-a:duration-100 hover:prose-a:text-emerald-500 prose-strong:text-gray-700 before:prose-code:content-[''] after:prose-code:content-[''] prose-code:font-normal prose-code:text-gray-700 prose-code:bg-slate-200 prose-code:px-1 prose-code:rounded prose-code:tracking-tight"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
42
src/pages/articles/[article].astro
Normal file
42
src/pages/articles/[article].astro
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
import ContainerLayout from "@layouts/ContainerLayout.astro";
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import TextContentLayout from "@layouts/TextContentLayout.astro";
|
||||
import type { GetStaticPaths } from "astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import ProseLayout from "@layouts/ProseLayout.astro";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const blogCollection = await getCollection("blog");
|
||||
return blogCollection.map((entry) => ({
|
||||
params: {
|
||||
article: entry.slug,
|
||||
},
|
||||
props: { entry },
|
||||
}));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const project = Astro.props.entry;
|
||||
const { Content } = await project.render();
|
||||
const { title, date } = project.data;
|
||||
---
|
||||
|
||||
<BaseLayout {title}>
|
||||
<ContainerLayout>
|
||||
<TextContentLayout>
|
||||
<p class="mx-auto mb-4 text-gray-500">
|
||||
{
|
||||
new Date(date).toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
</p>
|
||||
|
||||
<ProseLayout>
|
||||
<Content />
|
||||
</ProseLayout>
|
||||
</TextContentLayout>
|
||||
</ContainerLayout>
|
||||
</BaseLayout>
|
||||
60
src/pages/articles/index.astro
Normal file
60
src/pages/articles/index.astro
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
import Paragraph from "@components/Paragraph.astro";
|
||||
import ContainerLayout from "@layouts/ContainerLayout.astro";
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import TextContentLayout from "@layouts/TextContentLayout.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
const articles = await getCollection("blog");
|
||||
---
|
||||
|
||||
<BaseLayout title="Programming">
|
||||
<ContainerLayout>
|
||||
<TextContentLayout>
|
||||
<h1
|
||||
class="mb-8 text-center font-serif text-3xl font-light tracking-wide text-gray-600 sm:text-4xl"
|
||||
>
|
||||
Articles
|
||||
</h1>
|
||||
|
||||
<Paragraph>
|
||||
Sometimes I write about technology, programming, software, or other
|
||||
things that interests me.
|
||||
</Paragraph>
|
||||
|
||||
<section class="sm:max-w-2xl">
|
||||
{
|
||||
articles
|
||||
.sort((p2, p1) => p1.data.date.getTime() - p2.data.date.getTime())
|
||||
.map(async (article) => {
|
||||
const { slug, data } = article;
|
||||
const { image, intro } = data;
|
||||
const { headings } = await article.render();
|
||||
const title = headings[0].text;
|
||||
return (
|
||||
<a
|
||||
href={`/articles/${slug}`}
|
||||
class="my-8 grid grid-cols-1 mx-auto max-w-xs sm:max-w-none sm:grid-cols-3 overflow-hidden rounded-lg bg-white shadow duration-100 hover:translate-x-1 hover:shadow-md group"
|
||||
>
|
||||
<Image
|
||||
src={image}
|
||||
alt={title}
|
||||
class="col-span-1 object-cover h-48 sm:h-full"
|
||||
/>
|
||||
|
||||
<div class="col-span-1 sm:col-span-2 px-4 py-5">
|
||||
<h2 class="mb-2 text-xl font-serif text-gray-700 sm:line-clamp-1 group-hover:underline underline-offset-4">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<p class="sm:line-clamp-3 text-gray-500">{intro}</p>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
})
|
||||
}
|
||||
</section>
|
||||
</TextContentLayout>
|
||||
</ContainerLayout>
|
||||
</BaseLayout>
|
||||
|
|
@ -1,18 +1,26 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import { Image } from "astro:assets";
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import Link from "@components/Link.astro";
|
||||
import Paragraph from "@components/Paragraph.astro";
|
||||
import SocialIcons from "@components/SocialIcons.astro";
|
||||
|
||||
import portraitImage from "@assets/images/portrait.jpg";
|
||||
|
||||
const articles = await getCollection("blog");
|
||||
const latestArticle = articles.sort(
|
||||
(p2, p1) => p1.data.date.getTime() - p2.data.date.getTime(),
|
||||
)[0];
|
||||
const latestArticleSlug = latestArticle.slug;
|
||||
const latestArticleTitle = (await latestArticle.render()).headings[0].text;
|
||||
const latestArticleImage = latestArticle.data.image;
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<div
|
||||
class="mx-auto flex max-w-5xl flex-col-reverse px-3 pb-8 pt-12 sm:px-6 md:flex-row"
|
||||
class="mx-auto flex max-w-5xl flex-col-reverse px-3 pb-6 pt-8 sm:px-6 md:flex-row items-stretch"
|
||||
>
|
||||
<section class="w-full md:w-1/2">
|
||||
<section class="w-full md:w-3/5 mb-10 mt-4">
|
||||
<h1
|
||||
class="mb-8 font-serif text-3xl font-light tracking-wide text-gray-600 sm:text-4xl"
|
||||
>
|
||||
|
|
@ -26,14 +34,15 @@ import portraitImage from "@assets/images/portrait.jpg";
|
|||
|
||||
<Paragraph>
|
||||
Here you will find some
|
||||
<Link href="/programming">programming projects</Link>, and a small
|
||||
<Link href="/programming">programming projects</Link>, a small
|
||||
<Link href="/photography">collection of photos</Link>
|
||||
I'm proud of.
|
||||
I'm proud of, and even a few
|
||||
<Link href="/articles">articles I've written</Link>.
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph>
|
||||
Want to get in touch?
|
||||
<Link href="mailto:hi@mikkelsvartveit.com">Contact me.</Link>
|
||||
<Link href="mailto:hi@mikkelsvartveit.com">Contact me</Link>.
|
||||
</Paragraph>
|
||||
|
||||
<div class="mt-8 space-x-3">
|
||||
|
|
@ -41,7 +50,7 @@ import portraitImage from "@assets/images/portrait.jpg";
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<figure class="px-4 md:w-1/2">
|
||||
<figure class="px-4 md:w-1/2 my-auto">
|
||||
<Image
|
||||
class="mx-auto mb-12 block w-full max-w-sm rounded-full border-4 border-white bg-gray-100 text-transparent shadow-lg shadow-gray-400 md:mx-0 md:ml-auto md:w-5/6"
|
||||
src={portraitImage}
|
||||
|
|
@ -54,12 +63,35 @@ import portraitImage from "@assets/images/portrait.jpg";
|
|||
</figure>
|
||||
</div>
|
||||
|
||||
<footer class="mb-12 mt-4 flex justify-center">
|
||||
<p class="text-xs text-gray-400">
|
||||
<h2 class="text-center text-lg mb-3 text-gray-500">Latest article</h2>
|
||||
<a
|
||||
href={`/articles/${latestArticleSlug}`}
|
||||
class="mx-auto block max-w-lg px-3 sm:px-6 text-gray-600"
|
||||
>
|
||||
<div
|
||||
class="flex bg-white shadow transition hover:shadow-md hover:underline underline-offset-4 rounded-lg overflow-hidden items-center"
|
||||
>
|
||||
<Image src={latestArticleImage} alt="" class="w-24 h-16 object-cover" />
|
||||
<h3 class="text-lg font-serif mx-3 line-clamp-2 leading-snug">
|
||||
{latestArticleTitle}
|
||||
</h3>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<footer class="mb-8 mt-20 flex justify-center w-full">
|
||||
<p class="text-xs text-gray-500">
|
||||
Built with
|
||||
<Link href="https://astro.build/" target="_blank">Astro</Link>
|
||||
<a
|
||||
class="underline underline-offset-1 hover:text-gray-400"
|
||||
href="https://astro.build/"
|
||||
target="_blank">Astro</a
|
||||
>
|
||||
and
|
||||
<Link href="https://tailwindcss.com/" target="_blank">Tailwind CSS</Link>.
|
||||
<a
|
||||
class="underline underline-offset-1 hover:text-gray-400"
|
||||
href="https://tailwindcss.com/"
|
||||
target="_blank">Tailwind CSS</a
|
||||
>.
|
||||
</p>
|
||||
</footer>
|
||||
</BaseLayout>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import BaseLayout from "@layouts/BaseLayout.astro";
|
|||
import TextContentLayout from "@layouts/TextContentLayout.astro";
|
||||
import type { GetStaticPaths } from "astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import ProseLayout from "@layouts/ProseLayout.astro";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const programmingEntries = await getCollection("programming");
|
||||
|
|
@ -73,11 +74,10 @@ const { title, date, technologies, website, repository } = project.data;
|
|||
)
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
class="prose prose-lg max-w-none font-serif prose-headings:text-gray-600 prose-h2:font-light prose-a:text-teal-600 prose-a:no-underline prose-a:duration-100 hover:prose-a:text-teal-500 hover:prose-a:underline before:prose-code:content-[''] after:prose-code:content-['']"
|
||||
>
|
||||
|
||||
<ProseLayout>
|
||||
<Content />
|
||||
</div>
|
||||
</ProseLayout>
|
||||
</TextContentLayout>
|
||||
</ContainerLayout>
|
||||
</BaseLayout>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const projects = await getCollection("programming");
|
|||
.map(({ data, slug }) => (
|
||||
<a
|
||||
href={`/programming/${slug}`}
|
||||
class="mx-auto overflow-hidden rounded-lg bg-white pb-8 shadow-lg duration-100 hover:-translate-y-1 hover:shadow-xl"
|
||||
class="mx-auto overflow-hidden rounded-lg bg-white pb-8 shadow-md duration-100 hover:-translate-y-1 hover:shadow-lg"
|
||||
>
|
||||
<Image
|
||||
src={data.image}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default {
|
|||
fontFamily: {
|
||||
sans: ["'Nunito'", ...defaultTheme.fontFamily.sans],
|
||||
serif: ["'Source Serif Pro'", ...defaultTheme.fontFamily.serif],
|
||||
mono: [...defaultTheme.fontFamily.mono],
|
||||
mono: ["'Source Code Pro'", ...defaultTheme.fontFamily.mono],
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue