Initial commit

main
Gabe Farrell 2 years ago committed by GitHub
commit 2b72c1295f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,44 @@
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitType": "docs",
"commitConvention": "angular",
"contributors": [
{
"login": "SterbenVD",
"name": "Vishal Vijay Devadiga",
"avatar_url": "https://avatars.githubusercontent.com/u/90999906?v=4",
"profile": "https://github.com/SterbenVD",
"contributions": [
"code"
]
},
{
"login": "khushChopra",
"name": "Khush Chopra",
"avatar_url": "https://avatars.githubusercontent.com/u/43996455?v=4",
"profile": "https://github.com/khushChopra",
"contributions": [
"code"
]
},
{
"login": "javadshoja",
"name": "Javad Shoja",
"avatar_url": "https://avatars.githubusercontent.com/u/57140027?v=4",
"profile": "http://jbrave.ir",
"contributions": [
"maintenance"
]
}
],
"contributorsPerLine": 7,
"skipCi": true,
"repoType": "github",
"repoHost": "https://github.com",
"projectName": "astro-portfolio-template",
"projectOwner": "MaeWolff"
}

@ -0,0 +1,14 @@
module.exports = {
extends: ["plugin:astro/recommended"],
overrides: [
{
files: ["*.astro"],
parser: "astro-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
extraFileExtensions: [".astro"],
},
rules: {},
},
],
};

@ -0,0 +1,17 @@
name: CI
on: [push, pull_request]
jobs:
check-astro:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: checks
uses: actions/setup-node@v3
with:
node-version: 18.14.1
cache: "npm"
- run: npm install
- run: npm run check

21
.gitignore vendored

@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

@ -0,0 +1 @@
18.14.1

@ -0,0 +1,14 @@
module.exports = {
plugins: [
require.resolve("prettier-plugin-tailwindcss", "prettier-plugin-astro"),
],
tailwindConfig: "./tailwind.config.js",
overrides: [
{
files: "*.astro",
options: {
parser: "astro",
},
},
],
};

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Maxence WOLFF
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,54 @@
# 👨‍🚀 Astro - Portfolio Template
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
![Template](https://github.com/MaeWolff/astro-portfolio-template/blob/main/public/opengraph-image.jpg)
This theme/template is designed and crafted by [me](https://www.maxencewolff.com).
NB: Additional color themes can also be configured on the `src/data/theme.ts` file.
## 🥷 Usage
- You can modify all the information in the files in the `data` folder (presentation, social links, projects list, colors).
- You can write articles in `markdown` format in the `content/posts` folder.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## Contributors ✨
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SterbenVD"><img src="https://avatars.githubusercontent.com/u/90999906?v=4?s=100" width="100px;" alt="Vishal Vijay Devadiga"/><br /><sub><b>Vishal Vijay Devadiga</b></sub></a><br /><a href="https://github.com/MaeWolff/astro-portfolio-template/commits?author=SterbenVD" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/khushChopra"><img src="https://avatars.githubusercontent.com/u/43996455?v=4?s=100" width="100px;" alt="Khush Chopra"/><br /><sub><b>Khush Chopra</b></sub></a><br /><a href="https://github.com/MaeWolff/astro-portfolio-template/commits?author=khushChopra" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://jbrave.ir"><img src="https://avatars.githubusercontent.com/u/57140027?v=4?s=100" width="100px;" alt="Javad Shoja"/><br /><sub><b>Javad Shoja</b></sub></a><br /><a href="#maintenance-javadshoja" title="Maintenance">🚧</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

@ -0,0 +1,19 @@
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import sitemap from "@astrojs/sitemap";
import robotsTxt from "astro-robots-txt";
import { SITE_URL } from "./src/data/config";
// https://astro.build/config
export default defineConfig({
integrations: [tailwind(), sitemap(), robotsTxt()],
site: SITE_URL,
markdown: {
syntaxHighlight: "shiki",
shikiConfig: {
theme: "nord",
wrap: false
}
}
});

13284
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,30 @@
{
"name": "astro-template-portfolio",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"check": "astro check",
"astro": "astro"
},
"dependencies": {
"@astrojs/sitemap": "^3.0.0",
"@astrojs/tailwind": "^5.0.0",
"@fontsource/open-sans": "^5.0.12",
"astro": "^3.1.0",
"astro-robots-txt": "^1.0.0",
"astro-seo": "0.8.0",
"tailwindcss": "^3.3.3"
},
"devDependencies": {
"@typescript-eslint/parser": "5.59.7",
"eslint": "8.41.0",
"eslint-plugin-astro": "0.27.1",
"prettier": "2.8.8",
"prettier-plugin-astro": "0.9.0",
"prettier-plugin-tailwindcss": "0.3.0"
}
}

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

@ -0,0 +1,36 @@
---
import removeTrailingSlash from "@/utils/removeTrailingSlash";
import presentation from "@/data/presentation";
const pathname = removeTrailingSlash(Astro.url.pathname);
---
<header class="z-40 flex w-full flex-col gap-2 md:flex-row md:justify-between">
<a href={`mailto:${presentation.mail}`} class="hover:text-neutral-100"
>{presentation.mail}</a
>
<nav role="navigation">
<ul role="list" class="flex flex-row gap-2">
<li>
<a
href="/"
class:list={{
["hover:text-neutral-100"]: true,
["text-neutral-100"]: pathname === "/",
}}>Home</a
>
</li>
<li>/</li>
<li>
<a
href="/posts"
class:list={{
["hover:text-neutral-100"]: true,
["text-neutral-100"]: pathname === "/posts",
}}>Posts</a
>
</li>
</ul>
</nav>
</header>

@ -0,0 +1,49 @@
---
import formatDate from "@/utils/formatDate";
type Props = {
title: string;
publishedAt: Date;
description: string;
slug: string;
};
const { title, publishedAt, description, slug } = Astro.props;
---
<a
class="group flex max-w-sm cursor-pointer flex-col gap-2 rounded-md border border-neutral-700 p-4 transition-all duration-300 hover:-translate-y-2 hover:border-neutral-400"
href={`/posts/${slug}`}
>
<div
class="flex w-full flex-col justify-between gap-2 md:flex-row md:items-center"
>
<p class="text-neutral-100">{title}</p>
<div class="flex flex-row items-center gap-4">
<p>{formatDate(publishedAt)}</p>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
class="transition-all duration-300 group-hover:translate-x-1"
>
<path
d="M5.25 12.75L12.75 5.25"
stroke="#999999"
stroke-linecap="round"
stroke-linejoin="round"></path>
<path
d="M5.25 5.25H12.75V12.75"
stroke="#999999"
stroke-linecap="round"
stroke-linejoin="round"></path>
</svg>
</div>
</div>
<p class="truncate">
{description}
</p>
</a>

@ -0,0 +1,62 @@
---
import type { Project } from "@/data/projects";
type Props = Project;
const { title, techs, link, isComingSoon } = Astro.props;
const formatTechs = (values: string[]) =>
values.toString().replaceAll(",", " • ");
---
<>
{
isComingSoon && (
<div class="t group flex w-full flex-col justify-between gap-2 rounded-md border border-neutral-700 p-4 md:flex-row md:items-center">
<div class="flex flex-col gap-2 md:flex-row md:items-center md:gap-4">
<p class="text-neutral-100">{title}</p>
<p>{formatTechs(techs)}</p>
</div>
<p class="w-fit rounded-md bg-neutral-900 px-4 py-1">Soon</p>
</div>
)
}
{
!isComingSoon && (
<a
class="group flex w-full cursor-pointer flex-col justify-between gap-2 rounded-md border border-neutral-700 p-4 transition-all duration-300 hover:-translate-y-2 hover:border-neutral-400 md:flex-row md:items-center"
href={link}
target="_blank"
rel="noreferrer"
>
<div class="flex flex-col gap-4 md:flex-row md:items-center">
<p class="text-neutral-100">{title}</p>
<p>{formatTechs(techs)}</p>
</div>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
class="transition-all duration-300 group-hover:translate-x-1"
>
<path
d="M5.25 12.75L12.75 5.25"
stroke="#999999"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M5.25 5.25H12.75V12.75"
stroke="#999999"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
)
}
</>

@ -0,0 +1,18 @@
---
import Link from "./shared/Link.astro";
import presentation from "@/data/presentation";
---
<ul role="list" class="flex flex-row gap-2">
{
presentation.socials.map((social, index) => (
<>
<li>
<Link href={social.link} label={social.label} />
</li>
{presentation.socials.length - 1 !== index && <li>/</li>}
</>
))
}
</ul>

@ -0,0 +1,63 @@
---
import { SEO } from "astro-seo";
import { SITE_URL } from "@/data/config";
import type { HeadTags } from "@/utils/types/HeadTags";
type Props = HeadTags;
const { title, description, noindex, og } = Astro.props;
const DEFAULT_TITLE_PAGE = "Astro - Portfolio template";
const DEFAULT_DESCRIPTION_PAGE =
"A minimal portfolio template built with Astro and Tailwindcss.";
const DEFAULT_URL_SITE = SITE_URL;
const openGraph = {
title: title || og?.title || DEFAULT_TITLE_PAGE,
type: og?.type || "website",
image: og?.image || "/opengraph-image.jpg",
alt: og?.alt || "astro portfolio template image",
url: DEFAULT_URL_SITE,
description: og?.description || DEFAULT_DESCRIPTION_PAGE,
};
---
<head>
<SEO
charset="UTF-8"
title={title || DEFAULT_TITLE_PAGE}
description={description || DEFAULT_DESCRIPTION_PAGE}
noindex={noindex || false}
openGraph={{
basic: {
title: openGraph.title,
type: openGraph.type,
image: openGraph.image,
},
image: {
alt: openGraph.alt,
},
}}
twitter={{
creator: "@itsstormzz_",
}}
extend={{
link: [
{ rel: "icon", href: "/favicon.svg" },
{ rel: "sitemap", href: "/sitemap-index.xml" },
],
meta: [
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ name: "generator", content: Astro.generator },
{
name: "twitter:image",
content: openGraph.image,
},
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: openGraph.title },
{ name: "twitter:description", content: openGraph.description },
{ name: "twitter:site", content: "@itsstormzz_" },
],
}}
/>
</head>

@ -0,0 +1,20 @@
---
import type { TailwindColor } from "@/utils/types/tailwind";
import { MAP_COLOR_VARIANT_TO_BG } from "@/utils/mapVariants";
type Props = {
position: "bottom" | "top";
color: TailwindColor;
};
const { position, color } = Astro.props;
---
<div
class:list={{
[`fixed z-0 h-[134px] w-[134px] lg:w-[300px] lg:h-[300px] rounded-full ${MAP_COLOR_VARIANT_TO_BG[color]} blur-[150px] md:blur-[350px] opacity-50`]: true,
["left-0 top-0"]: position === "top",
["right-0 bottom-0"]: position === "bottom",
}}
>
</div>

@ -0,0 +1,24 @@
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"a"> & {
label: string;
isUnderline?: boolean;
};
const { label, isUnderline, ...props } = Astro.props;
---
<a
href={props.href}
class:list={{
["hover:text-neutral-100 cursor-pointer"]: true,
["underline decoration-dashed underline-offset-8"]: isUnderline,
}}
{...props}
rel="noreferrer"
target="_blank"
>
{label}
<span class="sr-only">{label} link</span>
</a>

@ -0,0 +1,14 @@
import { defineCollection, z } from "astro:content";
const postsCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
publishedAt: z.date(),
description: z.string(),
isPublish: z.boolean(),
isDraft: z.boolean().default(false),
}),
});
export const collections = { posts: postsCollection };

@ -0,0 +1,54 @@
---
title: "The Power Of React Hooks"
publishedAt: 2023-05-24
description: "Lorem ipsum dolor sit amet consectetur et ultrices blandit neque ege"
slug: "the-power-of-react-hooks"
isPublish: true
---
## Vocesque meum remis est neque Neptunus monte
Lorem markdownum nunc _adfuit_ fecisse, `quae pectus`, quod seu mortale suo
Minerva iussit obortas. Favilla victa; alarum signis barbara, nec _sibi dentes_
hostes?
## Corporibus Leucon
Quaeque viridis, pariter possit. Velatus Thetis, ab Buten, in et ite positis
annis ut Troasque altaque. Ancaeus convertit conscia Phinea petis. Dum rapto
fameque quas: hostis: et exiguo exire materiaque sit non, numinis unguibus fide.
Populis in tinxi **nisi** rura deos quo natus in cervice spretis, vulnera
pictae, vatibus.
> Illi tenebras si vultum suae. Matrem iam: iniqua adire, tetigere meque,
> cessant, gerebat.
## Montes ignarus precor rogabam primus ridet sanguine
In vir indefessus et patrios veniam. Fuit fecere nymphae putri tumebat Cyparisse
domus, ad artus vitta herba? Et gaudet pressum aeterna animam. Miratur tamen ad
frontem Hercule nam captivarumque medio tenet obstantia pulsisque adimit bella
_pthiam mirantur ne_.
> Lacertis et nomenque oracla exstabat: genitor nitor! Fluctus habes extinctum.
> Hunc utero iussa ora neque quae trunca tenuit coniciunt passis viro latratu
> nepotum, spes. Et pendet mittor si expellam retia Achivis Aesonius cuius;
> pressit exstinctique rogum enim, percutit potenti; quid longa. Nostra animaque
> genetrice viae, quam virus sermone in videri.
## Rapit harundine vana
Noctis et et carpis corpus amplexus; imagine indignanda pedum sospes; cornua
super **et simus**. Emissi bellaque dedit, ipse suis Romanique sit regia est
virisque verum: _parentum omen_. Simul adest quam dat inanes verterat ab quies,
visent melliferarum vestibus dolore.
Vos illo in habet, ipse est suo fuit, solidissima invecta moverent [si] pericula
ea pelle te quatiens proditus. Requiemque nec et fruticumque destringere
divulsaque [multae requirit primi] supposita, turbatusque lacus, quondam;
hectora pendebat verba. Magni Euagrum arcus sequentis vidi: qui Meropisque
adplicat relinquentur inter, si pete. Magna constitit ore rediit et parentis
pomaria lumina seque aura.
[multae requirit primi]: http://heu.io/
[si]: http://infelixlucina.net/mutati

@ -0,0 +1,9 @@
---
title: "Untitled"
publishedAt: 2023-05-24
description: "Lorem ipsum dolor sit amet consectetur et ultrices blandit neque ege"
slug: "untitled"
isPublish: true
---
## Hello World

@ -0,0 +1 @@
export const SITE_URL = "https://demo.maxencewolff.com/";

@ -0,0 +1,36 @@
type Social = {
label: string;
link: string;
};
type Presentation = {
mail: string;
title: string;
description: string;
socials: Social[];
profile?: string;
};
const presentation: Presentation = {
mail: "maxencewolff.pro@gmail.com",
title: "Hi, Im Maxence 👋",
// profile: "/profile.webp",
description:
"Bonjour, i'm a *french frontend developer* with over *3 years* of web experience. I am currently working with *NextJS and Typescript*. Outside of work I complete my pokemon card collection and learning TypeScript.",
socials: [
{
label: "X",
link: "https://twitter.com/itsstormzz_",
},
{
label: "Bento",
link: "https://bento.me/m-wolff",
},
{
label: "Github",
link: "https://github.com/MaeWolff",
},
],
};
export default presentation;

@ -0,0 +1,27 @@
export type Project = {
title: string;
techs: string[];
link: string;
isComingSoon?: boolean;
};
const projects: Project[] = [
{
title: "Dictionary App",
techs: ["ReactJS (NextJS)", "react-query", "zod"],
link: "https://github.com/MaeWolff/dictionary-app",
},
{
title: "Portfolio / Lina BLIDI",
techs: ["ReactJS (NextJS)", "TypeScript"],
link: "https://www.linablidi.fr/",
},
{
title: "Portfolio / Template",
techs: ["Astro"],
link: "/",
isComingSoon: true,
},
];
export default projects;

@ -0,0 +1,23 @@
import type { TailwindColor } from "@/utils/types/tailwind";
type Theme = {
colors: {
primary: TailwindColor;
blur: {
top: TailwindColor;
bottom: TailwindColor;
};
};
};
const theme: Theme = {
colors: {
primary: "orange",
blur: {
top: "orange",
bottom: "violet",
},
},
};
export default theme;

2
src/env.d.ts vendored

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

@ -0,0 +1,34 @@
---
import Header from "../components/Header.astro";
import BlurCircle from "@/components/shared/BlurCircle.astro";
import theme from "@/data/theme";
import SEOTags from "@/components/seo/SEOTags.astro";
import type { HeadTags } from "@/utils/types/HeadTags";
import "@fontsource/open-sans";
import "@/styles/tailwind.css";
import "@/styles/post.css";
export type Props = HeadTags;
const headTags = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<SEOTags {...headTags} />
<body
class="mx-auto flex min-h-screen max-w-[872px] flex-col gap-9 bg-neutral-950 px-10 py-8 text-sm text-neutral-400 md:gap-20 md:py-16"
>
<Header />
<BlurCircle position="top" color={theme.colors.blur.top} />
<BlurCircle position="bottom" color={theme.colors.blur.bottom} />
<slot />
<style is:global>
html {
font-family: "Open Sans";
}
</style>
</body>
</html>

@ -0,0 +1,105 @@
---
import { getCollection } from "astro:content";
import { Image } from "astro:assets"
import Layout from "@/layouts/Layout.astro";
import SocialLinks from "@/components/SocialLinks.astro";
import PostCard from "@/components/PostCard.astro";
import ProjectCard from "@/components/ProjectCard.astro";
import Link from "@/components/shared/Link.astro";
import convertAsteriskToStrongTag from "@/utils/convertAsteriskToStrongTag";
import presentation from "@/data/presentation";
import projects from "@/data/projects";
const posts = (await getCollection("posts")).sort(function (first, second) {
return second.data.publishedAt.getTime() - first.data.publishedAt.getTime();
});
---
<Layout>
<main class="flex flex-col gap-20">
<article
class="flex flex-col gap-8 md:flex-row-reverse md:justify-end md:gap-12"
>
{
presentation.profile && (
<Image
src={presentation.profile}
class="w-1/4 self-center rounded-full"
alt="Your Profile"
width="200"
height="200"
/>
)
}
<div class="flex flex-col gap-8">
<h1 class="text-3xl text-neutral-100">
{presentation.title}
</h1>
<h2
class="w-auto max-w-[60ch] leading-6"
set:html={convertAsteriskToStrongTag(presentation.description)}
/>
<SocialLinks />
</div>
</article>
<article class="flex flex-col gap-8">
<header class="flex w-full flex-row justify-between gap-2">
<h3 class="text-lg text-neutral-100">Latest posts</h3>
<Link href="/posts" label="See all posts" isUnderline target="_self" />
</header>
{posts.length === 0 && <p>Soon, stay connected 👀...</p>}
<section class="flex flex-col gap-4 md:flex-row md:flex-wrap">
{
posts.length !== 0 &&
posts
.slice(0, 2)
.map((post) => (
<PostCard
publishedAt={post.data.publishedAt}
title={post.data.title}
description={post.data.description}
slug={post.slug}
/>
))
}
</section>
</article>
<article class="flex flex-col gap-8">
<header class="flex w-full flex-row justify-between gap-2">
<h3 class="text-lg text-neutral-100">
Selected projects ({projects.length})
</h3>
</header>
{projects.length === 0 && <p>Oops, I must work^^^^^</p>}
<section class="flex flex-col gap-4">
{
projects.length !== 0 &&
projects.map((project) => <ProjectCard {...project} />)
}
</section>
</article>
<article class="flex flex-col gap-8">
<header class="flex w-full flex-row justify-between gap-2">
<h3 class="text-lg text-neutral-100">Get in touch</h3>
</header>
<p>
Email me at <Link
href={`mailto:${presentation.mail}`}
label={presentation.mail}
/> or follow me via my social links.
</p>
<SocialLinks />
</article>
</main>
</Layout>

@ -0,0 +1,34 @@
---
import { CollectionEntry, getCollection } from "astro:content";
import Layout from "@/layouts/Layout.astro";
import formatDate from "@/utils/formatDate";
export async function getStaticPaths() {
const posts = await getCollection("posts");
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
type Props = {
post: CollectionEntry<"posts">;
};
const { post } = Astro.props;
const { Content } = await post.render();
---
<Layout title={post.data.title} description={post.data.description}>
<main class="post mx-auto flex w-full max-w-prose flex-col gap-4">
<header role="presentation">
<h1 class="text-md">
{post.data.title} - {formatDate(post.data.publishedAt)}
</h1>
<p class="italic">{post.data.description}</p>
</header>
<Content />
</main>
</Layout>

@ -0,0 +1,55 @@
---
import { getCollection } from "astro:content";
import Layout from "@/layouts/Layout.astro";
import formatDate from "@/utils/formatDate";
const posts = (await getCollection("posts")).sort(function (first, second) {
return second.data.publishedAt.getTime() - first.data.publishedAt.getTime();
});
---
<Layout title="Template - All Posts">
<main class="flex flex-col gap-20">
<h1 class="text-2xl text-neutral-100">Posts</h1>
<article class="flex flex-col gap-4">
{
posts.map((post) => (
<a
class="group flex flex-col gap-2 border-t border-neutral-700 py-4 transition-all hover:text-neutral-100"
href={`/posts/${post.slug}`}
>
<div class="flex w-full items-center justify-between">
<h2 class="text-lg">{post.data.title}</h2>
<div class="flex flex-row items-center gap-4">
<p>{formatDate(post.data.publishedAt)}</p>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
class="transition-all duration-300 group-hover:translate-x-1"
>
<path
d="M5.25 12.75L12.75 5.25"
stroke="#999999"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M5.25 5.25H12.75V12.75"
stroke="#999999"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
</div>
<p>{post.data.description}</p>
</a>
))
}
</article>
</main>
</Layout>

@ -0,0 +1,39 @@
.post p {
@apply leading-7;
}
.post code {
@apply rounded-sm bg-neutral-800 px-1 py-[2px] text-orange-500;
}
.post h2 {
@apply mt-6 text-3xl text-neutral-100;
}
.post h3 {
@apply mt-4 text-xl text-neutral-100;
}
.post code {
@apply rounded-sm bg-neutral-800 px-1 py-[2px] text-red-500;
}
.post a {
@apply text-neutral-100 underline;
}
.post ul {
@apply list-disc;
}
.post blockquote {
@apply flex flex-row gap-2 before:block before:h-auto before:w-1 before:max-w-[1px] before:bg-red-400 before:content-[''];
}
.post pre {
@apply p-4;
}
.post pre > code {
@apply bg-transparent;
}

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@ -0,0 +1,11 @@
import theme from "@/data/theme";
import { MAP_COLOR_VARIANT_TO_TEXT } from "./mapVariants";
export default function convertAsteriskToStrongTag(str: string) {
return str.replace(
/\*{1,2}(.*?)\*{1,2}/g,
`<strong class="font-normal ${
MAP_COLOR_VARIANT_TO_TEXT[theme.colors.primary]
}">$1</strong>`
);
}

@ -0,0 +1,3 @@
export default function formatDate(date: Date) {
return new Intl.DateTimeFormat("en-GB").format(date);
}

@ -0,0 +1,41 @@
import type { TailwindColor } from "./types/tailwind";
const MAP_COLOR_VARIANT_TO_BG: Record<TailwindColor, string> = {
orange: "bg-orange-500",
violet: "bg-violet-500",
red: "bg-red-500",
amber: "bg-amber-500",
yellow: "bg-yellow-500",
lime: "bg-lime-500",
green: "bg-green-500",
emerald: "bg-emerald-500",
teal: "bg-violet-500",
cyan: "bg-cyan-500",
blue: "bg-blue-500",
indigo: "bg-indigo-500",
purple: "bg-purple-500",
fushia: "bg-fushia-500",
pink: "bg-pink-500",
rose: "bg-rose-500",
};
const MAP_COLOR_VARIANT_TO_TEXT: Record<TailwindColor, string> = {
orange: "text-orange-500",
violet: "text-violet-500",
red: "text-red-500",
amber: "text-amber-500",
yellow: "text-yellow-500",
lime: "text-lime-500",
green: "text-green-500",
emerald: "text-emerald-500",
teal: "text-violet-500",
cyan: "text-cyan-500",
blue: "text-blue-500",
indigo: "text-indigo-500",
purple: "text-purple-500",
fushia: "text-fushia-500",
pink: "text-pink-500",
rose: "text-rose-500",
};
export { MAP_COLOR_VARIANT_TO_BG, MAP_COLOR_VARIANT_TO_TEXT };

@ -0,0 +1,5 @@
export default function removeTrailingSlash(pathname: string) {
const matchTrailingSlash = /\w+\/$/;
if (matchTrailingSlash.test(pathname)) return pathname.slice(0, -1);
return pathname;
}

@ -0,0 +1,12 @@
export type HeadTags = {
title?: string;
description?: string;
noindex?: boolean;
og?: {
title: string;
type: string;
description: string;
image: string;
alt: string;
};
};

@ -0,0 +1,17 @@
export type TailwindColor =
| "orange"
| "violet"
| "red"
| "amber"
| "yellow"
| "lime"
| "green"
| "emerald"
| "teal"
| "cyan"
| "blue"
| "indigo"
| "purple"
| "fushia"
| "pink"
| "rose";

@ -0,0 +1,14 @@
const defaultTheme = require("tailwindcss/defaultTheme");
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
theme: {
extend: {
fontFamily: {
sans: ["Open Sans", ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [],
};

@ -0,0 +1,9 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Loading…
Cancel
Save