feat: init

This commit is contained in:
2026-04-09 20:41:06 -05:00
commit 3f49236deb
60 changed files with 3989 additions and 0 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
.astro
.vscode
node_modules
dist

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# ide
.vscode/
site-profile.code-workspace

17
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,17 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-merge-conflict
- id: check-json
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.11
hooks:
- id: prettier
types_or: [javascript, typescript, css, scss, html, json, yaml, markdown]
additional_dependencies:
- prettier
- prettier-plugin-astro
- prettier-plugin-tailwindcss

18
.releaserc.json Normal file
View File

@@ -0,0 +1,18 @@
{
"branches": ["release"],
"tagFormat": "${version}",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"semantic-release-export-data",
["@semantic-release/npm", { "npmPublish": false }],
["@semantic-release/git", {
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
["@saithodev/semantic-release-gitea", {
"giteaUrl": "https://gitea.alexlebens.dev"
}]
]
}

0
CHANGELOG.md Normal file
View File

35
Dockerfile Normal file
View File

@@ -0,0 +1,35 @@
ARG REGISTRY=dhi.io
FROM ${REGISTRY}/bun:1.3.11-alpine3.22-dev AS builder
WORKDIR /app
COPY package.json bun.lock ./
FROM builder AS prod-deps
RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
bun install --production --frozen-lockfile
FROM builder AS build-deps
RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
bun install --frozen-lockfile
FROM build-deps AS build
COPY . .
RUN bun run build
FROM ${REGISTRY}/bun:1.3.11-alpine3.22 AS runtime
WORKDIR /app
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
ARG APP_VERSION=latest
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=4321
LABEL version=$APP_VERSION
LABEL description="Astro based personal website"
EXPOSE $PORT
CMD ["bun", "run", "./dist/server/entry.mjs"]

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2026 Alex Lebens
Copyright (c) 2023 Tristen Tomczak
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.

73
README.md Normal file
View File

@@ -0,0 +1,73 @@
# 🌌 Milky-Way
Milky Way brings forth a minimalist design ethos, allowing your work to shine like stars in the night sky. With clean lines and intuitive navigation, visitors are guided effortlessly through your portfolio, focusing solely on your creations.
Embrace the whimsical charm of Milky Way as it showcases your talents in a manner that's both elegant and endearing. Whether you're a designer, developer, artist, or creative professional of any kind, Milky Way provides the perfect canvas to showcase your endeavors.
With its responsive design, Milky Way ensures a seamless experience across devices, from desktops to smartphones, so your portfolio is accessible to all who wish to explore it. Let your work take center stage against the backdrop of this celestial template.
<p align="center">
<img align="center" alt="Astro" src="https://portal.astro.build/_image?href=https%3A%2F%2Fstorage.googleapis.com%2Fdev-portal-bucket%2Fli8pypvydk9xnzzrtrgzfw9q11j836g6y9efm8.webp&w=1600&h=900&f=webp"/>
</p>
[![Built with Astro](https://astro.badg.es/v2/built-with-astro/tiny.svg)](https://astro.build) [![Netlify Status](https://api.netlify.com/api/v1/badges/0b0bcb79-a1d8-4b32-9566-8f30af19e4cc/deploy-status)](https://app.netlify.com/sites/astro-milky-way/deploys)
## 🔥 Features
- [x] Simple and clean design, perfect for showcasing your work.
- [x] Responsive layout for seamless viewing across different devices.
- [x] Fast and efficient, thanks to the Astro static site generator.
- [x] Easy to customize with CSS and straightforward HTML structure.
## ⚓ Lighthouse Score
<p align="center">
<img width="600" alt="Lighthouse Score" src="https://raw.githubusercontent.com/ttomczak3/Milky-Way/6e386e2f920c993c33d348a9c1271a1cec6c6d2b/milkyway-lighthouse-score.svg"/>
</p>
## 🚀 Getting Started
Clone this repository to your local machine using Git.
```scheme
git clone https://github.com/ttomczak3/Milky-Way.git
cd Milky-Way
```
| 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 |
Edit the HTML files in the `src/pages` directory to add your projects, experiences, and personal information. You can also modify the CSS styles in `src/styles` to match your preferences.
## 📂 Project Structure
```
/
├── public/
│ └── GitHub.webp
│ └── blog-post.webp
│ └── blog.webp
│ └── favicon.svg
│ └── laptop.webp
│ └── space.webp
│ └── youtube.webp
├── src/
│ ├── components/
│ ├── content/
│ ├── layouts/
│ ├── pages/
│ ├── styles/
│ └── env.d.ts
└── package.json
```
## 💻 Contributing
Contributions to this project are welcome. If you find a bug or have a suggestion for improvement, please open an issue or submit a pull request.
## 📃 License
This project is licensed under the MIT License. See the `LICENSE` file for details.
## ☕ Support
If you enjoy Milky-Way and would like to show your support and appreciation through a tip, I would gratefully accept it.
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/X8X0P7FGR)

4
astro.config.mjs Normal file
View File

@@ -0,0 +1,4 @@
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({});

2155
bun.lock Normal file

File diff suppressed because it is too large Load Diff

41
package.json Normal file
View File

@@ -0,0 +1,41 @@
{
"name": "milky-way",
"type": "module",
"version": "1.0.0",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "10.0.4",
"@astrojs/sitemap": "3.7.2",
"@tailwindcss/postcss": "4.2.2",
"@tailwindcss/vite": "4.2.2",
"astro": "6.1.5",
"tailwindcss": "4.2.2"
},
"devDependencies": {
"@saithodev/semantic-release-gitea": "2.1.0",
"@semantic-release/changelog": "6.0.3",
"@semantic-release/commit-analyzer": "13.0.1",
"@semantic-release/git": "10.0.1",
"@semantic-release/npm": "13.1.5",
"@semantic-release/release-notes-generator": "14.1.0",
"@tailwindcss/forms": "0.5.11",
"@tailwindcss/typography": "0.5.19",
"@typescript-eslint/parser": "8.58.1",
"eslint": "10.2.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-astro": "1.7.0",
"prettier": "3.8.1",
"prettier-plugin-astro": "0.14.1",
"prettier-plugin-tailwindcss": "0.7.2",
"semantic-release": "25.0.3",
"semantic-release-export-data": "1.2.0",
"typescript": "6.0.2",
"typescript-eslint": "8.58.1"
}
}

8
postcss.config.mjs Normal file
View File

@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};
export default config;

23
prettier.config.mjs Normal file
View File

@@ -0,0 +1,23 @@
/** @type {import("prettier").Config} */
const config = {
printWidth: 100,
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
useTabs: false,
plugins: [
'prettier-plugin-astro',
'prettier-plugin-tailwindcss',
],
overrides: [
{
files: '*.astro',
options: {
parser: 'astro',
},
},
],
};
export default config;

2
public/_headers Normal file
View File

@@ -0,0 +1,2 @@
/images/*
Cache-Control: public, max-age=31536000, immutable

BIN
public/images/404.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
public/images/GitHub.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
public/images/blog.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View File

@@ -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

View File

@@ -0,0 +1,7 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96" width="98" height="96">
<title>github-mark-white-svg</title>
<style>
.s0 { fill: #fdebf3 }
</style>
<path id="Layer" fill-rule="evenodd" class="s0" d="m48.9 0c-27.1 0-48.9 22-48.9 49.2 0 21.8 14 40.2 33.4 46.7 2.4 0.5 3.3-1.1 3.3-2.4 0-1.1-0.1-5-0.1-9.1-13.5 3-16.4-5.8-16.4-5.8-2.2-5.8-5.4-7.2-5.4-7.2-4.4-3 0.3-3 0.3-3 5 0.3 7.5 5 7.5 5 4.4 7.5 11.5 5.4 14.3 4.1 0.4-3.2 1.7-5.4 3.1-6.6-10.9-1.1-22.3-5.4-22.3-24.3 0-5.4 2-9.8 5-13.2-0.5-1.2-2.2-6.3 0.5-13 0 0 4.1-1.3 13.4 5q1.5-0.4 3-0.7 1.6-0.3 3.1-0.5 1.5-0.2 3.1-0.3 1.5-0.1 3.1-0.1c4.1 0 8.3 0.6 12.2 1.6 9.3-6.3 13.4-5 13.4-5 2.7 6.7 1 11.8 0.5 13 3.1 3.4 5 7.8 5 13.2 0 18.9-11.4 23.1-22.3 24.3 1.8 1.5 3.3 4.5 3.3 9.1 0 6.6-0.1 11.9-0.1 13.5 0 1.3 0.9 2.9 3.3 2.4 19.4-6.5 33.4-24.9 33.4-46.7 0.1-27.2-21.8-49.2-48.7-49.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 874 B

View File

@@ -0,0 +1,7 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96" width="98" height="96">
<title>github-mark-white-svg</title>
<style>
.s0 { fill: #1e1e2e }
</style>
<path id="Layer" fill-rule="evenodd" class="s0" d="m48.9 0c-27.1 0-48.9 22-48.9 49.2 0 21.8 14 40.2 33.4 46.7 2.4 0.5 3.3-1.1 3.3-2.4 0-1.1-0.1-5-0.1-9.1-13.5 3-16.4-5.8-16.4-5.8-2.2-5.8-5.4-7.2-5.4-7.2-4.4-3 0.3-3 0.3-3 5 0.3 7.5 5 7.5 5 4.4 7.5 11.5 5.4 14.3 4.1 0.4-3.2 1.7-5.4 3.1-6.6-10.9-1.1-22.3-5.4-22.3-24.3 0-5.4 2-9.8 5-13.2-0.5-1.2-2.2-6.3 0.5-13 0 0 4.1-1.3 13.4 5q1.5-0.4 3-0.7 1.6-0.3 3.1-0.5 1.5-0.2 3.1-0.3 1.5-0.1 3.1-0.1c4.1 0 8.3 0.6 12.2 1.6 9.3-6.3 13.4-5 13.4-5 2.7 6.7 1 11.8 0.5 13 3.1 3.4 5 7.8 5 13.2 0 18.9-11.4 23.1-22.3 24.3 1.8 1.5 3.3 4.5 3.3 9.1 0 6.6-0.1 11.9-0.1 13.5 0 1.3 0.9 2.9 3.3 2.4 19.4-6.5 33.4-24.9 33.4-46.7 0.1-27.2-21.8-49.2-48.7-49.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 874 B

BIN
public/images/image-1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/images/image-2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/images/laptop.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/images/li-in.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,8 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20">
<title>mail-white-svg</title>
<style>
.s0 { fill: #fdebf3 }
</style>
<path id="path2" fill-rule="evenodd" class="s0" d="m19 4v12h-18v-12zm-1 1h-16v10h16z"/>
<path id="path3" fill-rule="evenodd" class="s0" d="m1.1 4.1h17.8v11.8h-17.8zm16.9 7.1c0-3.5 0-3.8-0.1-3.7-0.1 0-1.9 0.9-4 2-2.1 1.1-3.9 2-3.9 2 0 0-1.8-0.9-3.9-2-2.1-1.1-3.9-2-4-2-0.1-0.1-0.1 0.2-0.1 3.7v3.9h16zm-2.5-3.6l2.5-1.3v-1.3h-16v1.3l4 2.1 4 2.1 1.5-0.8c0.8-0.4 2.6-1.4 4-2.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 557 B

8
public/images/mail.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20">
<title>mail-white-svg</title>
<style>
.s0 { fill: #1e1e2e }
</style>
<path id="path2" fill-rule="evenodd" class="s0" d="m19 4v12h-18v-12zm-1 1h-16v10h16z"/>
<path id="path3" fill-rule="evenodd" class="s0" d="m1.1 4.1h17.8v11.8h-17.8zm16.9 7.1c0-3.5 0-3.8-0.1-3.7-0.1 0-1.9 0.9-4 2-2.1 1.1-3.9 2-3.9 2 0 0-1.8-0.9-3.9-2-2.1-1.1-3.9-2-4-2-0.1-0.1-0.1 0.2-0.1 3.7v3.9h16zm-2.5-3.6l2.5-1.3v-1.3h-16v1.3l4 2.1 4 2.1 1.5-0.8c0.8-0.4 2.6-1.4 4-2.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 557 B

BIN
public/images/space.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

BIN
public/images/youtube.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

41
renovate.json Normal file
View File

@@ -0,0 +1,41 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"mergeConfidence:all-badges",
":rebaseStalePrs"
],
"timezone": "US/Central",
"labels": [],
"prHourlyLimit": 0,
"prConcurrentLimit": 0,
"rangeStrategy": "pin",
"packageRules": [
{
"description": "Label dependency",
"matchDatasources": [
"npm"
],
"addLabels": [
"dependency"
],
"automerge": false,
"minimumReleaseAge": "1 days"
},
{
"description": "Automerge dependency patch",
"matchDatasources": [
"npm"
],
"matchUpdateTypes": [
"patch"
],
"addLabels": [
"dependency",
"automerge"
],
"automerge": true,
"minimumReleaseAge": "1 days"
}
]
}

32
src/components/Card.astro Normal file
View File

@@ -0,0 +1,32 @@
---
interface Props {
url: string;
image: string;
title: string;
body: string;
}
const { url, image, title, body } = Astro.props;
---
<li class="card">
<a class="card__link" href={url}>
<div class="center">
<img
class="card__img"
src={image}
role="presentation"
width="226"
height="127"
loading="lazy"
decoding="async"
/>
</div>
<h3 class="card__title">
{title}
</h3>
<p class="card__txt">
{body}
</p>
</a>
</li>

View File

@@ -0,0 +1,29 @@
---
interface Props {
url: string;
image: string;
title: string;
date: string;
}
const { url, image, title, date } = Astro.props;
---
<li class="card">
<a class="card__link" href={url}>
<div class="center">
<img
class="card__img"
src={image}
role="presentation"
width="226"
height="127"
loading="lazy"
decoding="async"
/>
</div>
<h3 class="card__title">
{title}
</h3>
</a>
</li>

View File

@@ -0,0 +1,14 @@
---
const year = new Date().getFullYear();
---
<footer>
<div class="center">
<ul class="footer">
<li class="icon__btn"><a class="icon__link" href="#" target="_blank"><span class="git-icon">GitHub</span></a></li>
<li class="icon__btn"><a class="icon__link" href="mailto: #@gmail.com"><span class="mail-icon">Email</span></a></li>
<li class="icon__btn"><a class="icon__link" href="#" target="_blank"><span class="linked-in">LinkedIn</span></a></li>
</ul>
<small>&copy; {year} Milky-Way by <a class="footer__link" href="https://github.com/ttomczak3">ttomczak</a>. All Rights Reserved.</small>
</div>
</footer>

View File

@@ -0,0 +1,9 @@
---
import Navigation from './Navigation.astro';
---
<header>
<nav transition:persist >
<Navigation />
</nav>
</header>

View File

@@ -0,0 +1,15 @@
---
import ThemeIcon from './ThemeIcon.astro';
---
<div class="navbar">
<div class="navbar__title">
<a href="/">Milky-Way</a>
</div>
<div class="navbar__menu">
<a href="/works/">Works</a>
<a href="/posts/">Posts</a>
<a href="https://github.com/ttomczak3/Milky-Way" target="_blank">GitHub</a>
<ThemeIcon />
</div>
</div>

View File

@@ -0,0 +1,55 @@
---
---
<button id="themeToggle" title="Theme Toggle">
<svg width="30px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path class="sun" fill-rule="evenodd" d="M12 17.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0 1.5a7 7 0 1 0 0-14 7 7 0 0 0 0 14zm12-7a.8.8 0 0 1-.8.8h-2.4a.8.8 0 0 1 0-1.6h2.4a.8.8 0 0 1 .8.8zM4 12a.8.8 0 0 1-.8.8H.8a.8.8 0 0 1 0-1.6h2.5a.8.8 0 0 1 .8.8zm16.5-8.5a.8.8 0 0 1 0 1l-1.8 1.8a.8.8 0 0 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM6.3 17.7a.8.8 0 0 1 0 1l-1.7 1.8a.8.8 0 1 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM12 0a.8.8 0 0 1 .8.8v2.5a.8.8 0 0 1-1.6 0V.8A.8.8 0 0 1 12 0zm0 20a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-1.6 0v-2.4a.8.8 0 0 1 .8-.8zM3.5 3.5a.8.8 0 0 1 1 0l1.8 1.8a.8.8 0 1 1-1 1L3.5 4.6a.8.8 0 0 1 0-1zm14.2 14.2a.8.8 0 0 1 1 0l1.8 1.7a.8.8 0 0 1-1 1l-1.8-1.7a.8.8 0 0 1 0-1z"/>
<path class="moon" fill-rule="evenodd" d="M16.5 6A10.5 10.5 0 0 1 4.7 16.4 8.5 8.5 0 1 0 16.4 4.7l.1 1.3zm-1.7-2a9 9 0 0 1 .2 2 9 9 0 0 1-11 8.8 9.4 9.4 0 0 1-.8-.3c-.4 0-.8.3-.7.7a10 10 0 0 0 .3.8 10 10 0 0 0 9.2 6 10 10 0 0 0 4-19.2 9.7 9.7 0 0 0-.9-.3c-.3-.1-.7.3-.6.7a9 9 0 0 1 .3.8z"/>
</svg>
</button>
<style>
#themeToggle {
border: 0;
background: none;
}
#themeToggle:hover {
cursor: pointer;
rotate: 10deg;
}
.moon { fill: #eff1f5; }
.sun { fill: transparent; }
:global(.light) .moon { fill: transparent; }
:global(.light) .sun { fill: #1e1e2e; }
</style>
<script is:inline >
const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
if (window.matchMedia('(prefers-color-scheme: light)').matches) {
return 'light';
}
return 'dark';
})();
if (theme === 'dark') {
document.documentElement.classList.remove('light');
} else {
document.documentElement.classList.add('light');
}
window.localStorage.setItem('theme', theme);
const handleToggleClick = () => {
const element = document.documentElement;
element.classList.toggle("light");
const isLight = element.classList.contains("light");
localStorage.setItem("theme", isLight ? "light" : "dark");
}
document.getElementById("themeToggle").addEventListener("click", handleToggleClick);
</script>

45
src/content.config.ts Normal file
View File

@@ -0,0 +1,45 @@
import { z, defineCollection } from "astro:content";
import { glob } from 'astro/loaders';
const projectsCollection = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/projects" }),
schema: z.object({
title: z.string(),
description: z.string(),
image: z.object({
url: z.string(),
alt: z.string()
}),
worksImage1: z.object({
url: z.string(),
alt: z.string()
}),
worksImage2: z.object({
url: z.string(),
alt: z.string()
}),
platform: z.string(),
stack: z.string(),
website: z.string(),
github: z.string(),
})
});
const postsCollection = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: "./src/content/posts" }),
schema: z.object({
title: z.string(),
author: z.string(),
date: z.string(),
image: z.object({
url: z.string(),
alt: z.string()
})
})
});
export const collections = {
projects: projectsCollection,
posts: postsCollection
};

View File

@@ -0,0 +1,22 @@
---
title: 'Post 1'
author: Tristen Tomczak
date: '03-23-2025'
image:
url: '/.netlify/images?url=/images/blog-post.webp'
alt: 'Post Thumbnail'
---
Aenean a ex et metus finibus malesuada commodo in magna. In ut libero urna. Aenean in quam in ipsum rutrum egestas. Donec semper dignissim ante. Sed efficitur mi et sapien ultrices malesuada. Aliquam fermentum aliquam ante, eu semper mi vestibulum quis. Sed et purus metus. Pellentesque vestibulum commodo euismod. Duis a mauris accumsan lorem laoreet tempor. Mauris accumsan varius metus, in rutrum magna accumsan eget. Pellentesque at leo at sem tempor hendrerit non sit amet ante. Cras commodo augue sed magna rutrum rutrum.
<div class="center">
<img class="pro-img" src="/.netlify/images?url=/images/image-1.webp" alt="First Image" width="500px" height="281" loading="lazy" decoding="async">
</div>
Vivamus sed faucibus lorem. Aenean a lorem convallis, ultrices nisl vitae, imperdiet elit. Duis in tristique lacus. Quisque sollicitudin dolor ac dui faucibus, ut tincidunt velit blandit. Donec tincidunt metus eros, at dignissim enim blandit ut. Cras varius tincidunt tortor ac tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed a ipsum quis nulla commodo pretium. Ut sed sem bibendum, facilisis dolor sit amet, interdum nibh. Aliquam id auctor dolor. In nulla diam, mattis quis nisl et, aliquam interdum quam. Donec lobortis ex arcu, ac pharetra ante vehicula euismod. Donec finibus faucibus felis vitae facilisis.
<div class="center">
<img class="pro-img" src="/.netlify/images?url=/images/image-2.webp" alt="Second Image" width="500px" height="281" loading="lazy" decoding="async">
</div>
Vivamus sed faucibus lorem. Aenean a lorem convallis, ultrices nisl vitae, imperdiet elit. Duis in tristique lacus. Quisque sollicitudin dolor ac dui faucibus, ut tincidunt velit blandit. Donec tincidunt metus eros, at dignissim enim blandit ut. Cras varius tincidunt tortor ac tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed a ipsum quis nulla commodo pretium. Ut sed sem bibendum, facilisis dolor sit amet, interdum nibh. Aliquam id auctor dolor. In nulla diam, mattis quis nisl et, aliquam interdum quam. Donec lobortis ex arcu, ac pharetra ante vehicula euismod. Donec finibus faucibus felis vitae facilisis.

View File

@@ -0,0 +1,22 @@
---
title: 'Post 2'
author: Tristen Tomczak
date: '03-23-2025'
image:
url: '/.netlify/images?url=/images/blog-post.webp'
alt: 'Post Thumbnail'
---
Aenean a ex et metus finibus malesuada commodo in magna. In ut libero urna. Aenean in quam in ipsum rutrum egestas. Donec semper dignissim ante. Sed efficitur mi et sapien ultrices malesuada. Aliquam fermentum aliquam ante, eu semper mi vestibulum quis. Sed et purus metus. Pellentesque vestibulum commodo euismod. Duis a mauris accumsan lorem laoreet tempor. Mauris accumsan varius metus, in rutrum magna accumsan eget. Pellentesque at leo at sem tempor hendrerit non sit amet ante. Cras commodo augue sed magna rutrum rutrum.
<div class="center">
<img class="pro-img" src="/.netlify/images?url=/images/image-1.webp" alt="First Image" width="500px" height="281" loading="lazy" decoding="async">
</div>
Vivamus sed faucibus lorem. Aenean a lorem convallis, ultrices nisl vitae, imperdiet elit. Duis in tristique lacus. Quisque sollicitudin dolor ac dui faucibus, ut tincidunt velit blandit. Donec tincidunt metus eros, at dignissim enim blandit ut. Cras varius tincidunt tortor ac tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed a ipsum quis nulla commodo pretium. Ut sed sem bibendum, facilisis dolor sit amet, interdum nibh. Aliquam id auctor dolor. In nulla diam, mattis quis nisl et, aliquam interdum quam. Donec lobortis ex arcu, ac pharetra ante vehicula euismod. Donec finibus faucibus felis vitae facilisis.
<div class="center">
<img class="pro-img" src="/.netlify/images?url=/images/image-2.webp" alt="Second Image" width="500px" height="281" loading="lazy" decoding="async">
</div>
Vivamus sed faucibus lorem. Aenean a lorem convallis, ultrices nisl vitae, imperdiet elit. Duis in tristique lacus. Quisque sollicitudin dolor ac dui faucibus, ut tincidunt velit blandit. Donec tincidunt metus eros, at dignissim enim blandit ut. Cras varius tincidunt tortor ac tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed a ipsum quis nulla commodo pretium. Ut sed sem bibendum, facilisis dolor sit amet, interdum nibh. Aliquam id auctor dolor. In nulla diam, mattis quis nisl et, aliquam interdum quam. Donec lobortis ex arcu, ac pharetra ante vehicula euismod. Donec finibus faucibus felis vitae facilisis.

View File

@@ -0,0 +1,22 @@
---
title: 'Post 3'
author: Tristen Tomczak
date: '03-23-2025'
image:
url: '/.netlify/images?url=/images/blog-post.webp'
alt: 'Post Thumbnail'
---
Aenean a ex et metus finibus malesuada commodo in magna. In ut libero urna. Aenean in quam in ipsum rutrum egestas. Donec semper dignissim ante. Sed efficitur mi et sapien ultrices malesuada. Aliquam fermentum aliquam ante, eu semper mi vestibulum quis. Sed et purus metus. Pellentesque vestibulum commodo euismod. Duis a mauris accumsan lorem laoreet tempor. Mauris accumsan varius metus, in rutrum magna accumsan eget. Pellentesque at leo at sem tempor hendrerit non sit amet ante. Cras commodo augue sed magna rutrum rutrum.
<div class="center">
<img class="pro-img" src="/.netlify/images?url=/images/image-1.webp" alt="First Image" width="500px" height="281" loading="lazy" decoding="async">
</div>
Vivamus sed faucibus lorem. Aenean a lorem convallis, ultrices nisl vitae, imperdiet elit. Duis in tristique lacus. Quisque sollicitudin dolor ac dui faucibus, ut tincidunt velit blandit. Donec tincidunt metus eros, at dignissim enim blandit ut. Cras varius tincidunt tortor ac tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed a ipsum quis nulla commodo pretium. Ut sed sem bibendum, facilisis dolor sit amet, interdum nibh. Aliquam id auctor dolor. In nulla diam, mattis quis nisl et, aliquam interdum quam. Donec lobortis ex arcu, ac pharetra ante vehicula euismod. Donec finibus faucibus felis vitae facilisis.
<div class="center">
<img class="pro-img" src="/.netlify/images?url=/images/image-2.webp" alt="Second Image" width="500px" height="281" loading="lazy" decoding="async">
</div>
Vivamus sed faucibus lorem. Aenean a lorem convallis, ultrices nisl vitae, imperdiet elit. Duis in tristique lacus. Quisque sollicitudin dolor ac dui faucibus, ut tincidunt velit blandit. Donec tincidunt metus eros, at dignissim enim blandit ut. Cras varius tincidunt tortor ac tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed a ipsum quis nulla commodo pretium. Ut sed sem bibendum, facilisis dolor sit amet, interdum nibh. Aliquam id auctor dolor. In nulla diam, mattis quis nisl et, aliquam interdum quam. Donec lobortis ex arcu, ac pharetra ante vehicula euismod. Donec finibus faucibus felis vitae facilisis.

View File

@@ -0,0 +1,22 @@
---
title: 'Post 4'
author: Tristen Tomczak
date: '03-23-2025'
image:
url: '/.netlify/images?url=/images/blog-post.webp'
alt: 'Post Thumbnail'
---
Aenean a ex et metus finibus malesuada commodo in magna. In ut libero urna. Aenean in quam in ipsum rutrum egestas. Donec semper dignissim ante. Sed efficitur mi et sapien ultrices malesuada. Aliquam fermentum aliquam ante, eu semper mi vestibulum quis. Sed et purus metus. Pellentesque vestibulum commodo euismod. Duis a mauris accumsan lorem laoreet tempor. Mauris accumsan varius metus, in rutrum magna accumsan eget. Pellentesque at leo at sem tempor hendrerit non sit amet ante. Cras commodo augue sed magna rutrum rutrum.
<div class="center">
<img class="pro-img" src="/.netlify/images?url=/images/image-1.webp" alt="First Image" width="500px" height="281" loading="lazy" decoding="async">
</div>
Vivamus sed faucibus lorem. Aenean a lorem convallis, ultrices nisl vitae, imperdiet elit. Duis in tristique lacus. Quisque sollicitudin dolor ac dui faucibus, ut tincidunt velit blandit. Donec tincidunt metus eros, at dignissim enim blandit ut. Cras varius tincidunt tortor ac tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed a ipsum quis nulla commodo pretium. Ut sed sem bibendum, facilisis dolor sit amet, interdum nibh. Aliquam id auctor dolor. In nulla diam, mattis quis nisl et, aliquam interdum quam. Donec lobortis ex arcu, ac pharetra ante vehicula euismod. Donec finibus faucibus felis vitae facilisis.
<div class="center">
<img class="pro-img" src="/.netlify/images?url=/images/image-2.webp" alt="Second Image" width="500px" height="281" loading="lazy" decoding="async">
</div>
Vivamus sed faucibus lorem. Aenean a lorem convallis, ultrices nisl vitae, imperdiet elit. Duis in tristique lacus. Quisque sollicitudin dolor ac dui faucibus, ut tincidunt velit blandit. Donec tincidunt metus eros, at dignissim enim blandit ut. Cras varius tincidunt tortor ac tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed a ipsum quis nulla commodo pretium. Ut sed sem bibendum, facilisis dolor sit amet, interdum nibh. Aliquam id auctor dolor. In nulla diam, mattis quis nisl et, aliquam interdum quam. Donec lobortis ex arcu, ac pharetra ante vehicula euismod. Donec finibus faucibus felis vitae facilisis.

View File

@@ -0,0 +1,19 @@
---
title: 'Project 1'
description: 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci'
image:
url: '/.netlify/images?url=/images/GitHub.webp'
alt: 'GitHub wallpaper'
worksImage1:
url: '/.netlify/images?url=/images/image-1.webp'
alt: 'first image of your project.'
worksImage2:
url: '/.netlify/images?url=/images/image-2.webp'
alt: 'second image of your project.'
platform: Web
stack: Astro, JavaScript
website: https://astro-milky-way.netlify.app/
github: https://github.com/ttomczak3/Milky-Way
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras faucibus a tortor at molestie. Sed pellentesque leo auctor, auctor lorem nec, venenatis risus. Vivamus commodo ipsum vitae orci finibus, vel porta nunc viverra. In hac habitasse platea dictumst. Nunc pretium, ligula ultricies consequat sollicitudin, enim ex ullamcorper nisl.

View File

@@ -0,0 +1,19 @@
---
title: 'Project 2'
description: 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci'
image:
url: '/.netlify/images?url=/images/GitHub.webp'
alt: 'GitHub wallpaper'
worksImage1:
url: '/.netlify/images?url=/images/image-1.webp'
alt: 'first image of your project.'
worksImage2:
url: '/.netlify/images?url=/images/image-2.webp'
alt: 'second image of your project.'
platform: Web
stack: Astro, JavaScript
website: https://astro-milky-way.netlify.app/
github: https://github.com/ttomczak3/Milky-Way
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras faucibus a tortor at molestie. Sed pellentesque leo auctor, auctor lorem nec, venenatis risus. Vivamus commodo ipsum vitae orci finibus, vel porta nunc viverra. In hac habitasse platea dictumst. Nunc pretium, ligula ultricies consequat sollicitudin, enim ex ullamcorper nisl.

2
src/env.d.ts vendored Normal file
View File

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

45
src/layouts/Layout.astro Normal file
View File

@@ -0,0 +1,45 @@
---
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import '../styles/global.css';
const { pageTitle } = Astro.props;
---
<!DOCTYPE html>
<html transition:animate="none" lang="en">
<head>
<meta charset="utf-8" />
<meta name="description" content="Astro description">
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Josefin+Sans&family=Pacifico&display=swap" as="style">
<link href="https://fonts.googleapis.com/css2?family=Josefin+Sans&family=Pacifico&display=swap" rel="stylesheet">
<meta name="generator" content={Astro.generator} />
<title>{pageTitle}</title>
</head>
<body>
<Header />
<main>
<slot />
<Footer />
</main>
<script>
function colorMode() {
if (
localStorage.theme === "light" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: light)").matches)
) {
document.documentElement.classList.add("light");
} else {
document.documentElement.classList.remove("light");
}
}
colorMode();
document.addEventListener('astro:after-swap', colorMode);
</script>
</body>
</html>

View File

@@ -0,0 +1,20 @@
---
import BaseLayout from "./Layout.astro";
const { frontmatter } = Astro.props;
---
<BaseLayout pageTitle={frontmatter.title}>
<div class="gif">
<img src="/.netlify/images?url=/images/blog.webp" role="presentation" width="300" height="259" decoding="async">
</div>
<h2 style="text-decoration:none;">{frontmatter.title}</h2>
<ul class="badge__list">
<li>
<span class="badge badge--item">AUTHOR</span>{frontmatter.author}
</li>
<li>
<span class="badge badge--item">PUBLISHED</span>{frontmatter.date}
</li>
</ul>
<slot />
</BaseLayout>

View File

@@ -0,0 +1,22 @@
---
import BaseLayout from './Layout.astro';
const { frontmatter } = Astro.props;
---
<BaseLayout pageTitle={frontmatter.title}>
<div class="gif">
<img src="/.netlify/images?url=/images/laptop.webp" role="presentation" width="300" height="259" decoding="async">
</div>
<h2>{frontmatter.title}</h2>
<slot />
<ul class="badge__list">
<li><span class="badge badge--item">PLATFORM</span>{frontmatter.platform}</li>
<li><span class="badge badge--item">STACK</span>{frontmatter.stack}</li>
<li><span class="badge badge--item">WEBSITE</span><a class="badge__link" href={frontmatter.website} target="_blank">{frontmatter.website}</a></li>
<li><span class="badge badge--item">GITHUB</span><a class="badge__link" href={frontmatter.github} target="_blank">{frontmatter.github}</a></li>
</ul>
<div class="center">
<img class="pro-img" width="500px" height="281" src={frontmatter.worksImage1.url} alt={frontmatter.worksImage1.alt} />
<img class="pro-img" width="500px" height="281" src={frontmatter.worksImage2.url} alt={frontmatter.worksImage2.alt} />
</div>
</BaseLayout>

17
src/pages/404.astro Normal file
View File

@@ -0,0 +1,17 @@
---
const description = "404";
import '../styles/global.css';
---
<layout>
<div class="lost">
<div class="gif">
<img src="/.netlify/images?url=/images/404.webp" role="presentation" width="300" height="259" decoding="async">
</div>
<h1 class="lost__header">{ description }</h1>
<p class="lost__body">It seems like you've wandered into the wrong forest!</p>
<p>
<a class="lost__link" href="/">Return Home</a>
</p>
</div>
</layout>

51
src/pages/index.astro Normal file
View File

@@ -0,0 +1,51 @@
---
import Layout from '../layouts/Layout.astro';
const pageTitle = "Milky-Way";
---
<Layout pageTitle={pageTitle}>
<div class="gif">
<img src="/.netlify/images?url=/images/space.webp" role="presentation" width="300" height="259" decoding="async">
</div>
<h1>Milky-Way</h1>
<span class="badge">Digital Craftsman (Developer)</span>
<h2>About</h2>
<p>
Praesent malesuada mi nisi, quis tempus magna venenatis ut.
Suspendisse tempor sem at scelerisque congue. Curabitur mollis,
mi id molestie finibus, metus sem sagittis nunc, euismod feugiat orci neque non lorem.
Aenean sollicitudin erat eu molestie tempus. Nunc ac eros elementum, fermentum lacus at,
pellentesque mauris. Suspendisse eros neque, interdum euismod consectetur convallis,
porttitor at massa. Phasellus a ultrices arcu. Donec viverra, lorem in pellentesque euismod,
libero justo gravida nibh, non ultricies justo purus ultrices est.
</p>
<p>
Ut at interdum dui. Donec ut ante ex. Maecenas id ex eget nibh consequat accumsan.
Proin semper semper dui nec feugiat. Donec cursus neque id aliquet finibus. Morbi
facilisis turpis nibh, nec dictum orci dictum nec. In hac habitasse platea dictumst.
Maecenas mollis turpis vitae turpis bibendum, vitae tristique arcu eleifend.
</p>
<div class="center">
<a href="/works/">
<button class="btn">My portfolio</button>
</a>
</div>
<h2>Skills</h2>
<div class="skills center">
<img class="skills__img" width="46" height="46" alt="Python" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/python/python-original.svg"/>
<img class="skills__img" width="46" height="46" alt="Golang" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/go/go-original-wordmark.svg"/>
<img class="skills__img" width="46" height="46" alt="JavaScript" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/javascript/javascript-original.svg"/>
<img class="skills__img" width="46" height="46" alt="TypeScript" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/typescript/typescript-original.svg"/>
<img class="skills__img" width="46" height="46" alt="Docker" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/docker/docker-original.svg"/>
<img class="skills__img" width="46" height="46" alt="Linux" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/linux/linux-original.svg"/>
<img class="skills__img" width="46" height="46" alt="Kubernetes" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/kubernetes/kubernetes-original.svg"/>
<img class="skills__img" width="46" height="46" alt="Git" src="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/git/git-original.svg"/>
</div>
<h2>Interests</h2>
<p>
Ut at interdum dui. Donec ut ante ex. Maecenas id ex eget nibh consequat accumsan.
Proin semper semper dui nec feugiat. Donec cursus neque id aliquet finibus. Morbi
facilisis turpis nibh, nec dictum orci dictum nec. In hac habitasse platea dictumst.
Maecenas mollis turpis vitae turpis bibendum, vitae tristique arcu eleifend.
</p>
</Layout>

26
src/pages/posts.astro Normal file
View File

@@ -0,0 +1,26 @@
---
import { getCollection } from "astro:content";
import Layout from "../layouts/Layout.astro";
import Card from "../components/CardPost.astro";
const allPosts = await getCollection("posts");
const pageTitle = "Posts";
---
<Layout pageTitle={pageTitle}>
<div class="gif">
<img src="/.netlify/images?url=/images/blog.webp" role="presentation" width="300" height="259" decoding="async">
</div>
<h2>Blog</h2>
<ul role="list" class="link-card-grid">
{
allPosts.map((cardPost) => (
<Card
url={`/posts/${cardPost.id}/`}
image={cardPost.data.image.url}
title={cardPost.data.title}
date={cardPost.data.date}
/>
))
}
</ul>
</Layout>

View File

@@ -0,0 +1,20 @@
---
import { getCollection, getEntry, render } from "astro:content";
import MarkdownPostsLayout from "../../layouts/MarkdownPostsLayout.astro";
export const prerender = true;
export async function getStaticPaths() {
const postEntries = await getCollection("posts");
return postEntries.map((entry) => ({
params: { slug: entry.id },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content, headings } = await render(entry);
---
<MarkdownPostsLayout frontmatter={entry.data}>
<Content />
</MarkdownPostsLayout>

View File

@@ -0,0 +1,20 @@
---
import { getCollection, getEntry, render } from "astro:content";
import MarkdownWorksLayout from "../../layouts/MarkdownWorksLayout.astro";
export const prerender = true;
export async function getStaticPaths() {
const projectEntries = await getCollection("projects");
return projectEntries.map((entry) => ({
params: { slug: entry.id },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content, headings } = await render(entry);
---
<MarkdownWorksLayout frontmatter={entry.data}>
<Content />
</MarkdownWorksLayout>

22
src/pages/works.astro Normal file
View File

@@ -0,0 +1,22 @@
---
import { getCollection } from "astro:content";
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
const allProjects = await getCollection("projects");
const pageTitle = "Works";
---
<Layout pageTitle={pageTitle}>
<div class="gif">
<img src="/.netlify/images?url=/images/laptop.webp" role="presentation" width="300" height="259" decoding="async">
</div>
<h2>Projects</h2>
<ul role="list" class="link-card-grid">
{allProjects.map((card) => <Card
url={`/projects/${card.id}/`}
image={card.data.image.url}
title={card.data.title}
body={card.data.description}
/>)}
</ul>
</Layout>

390
src/styles/global.css Normal file
View File

@@ -0,0 +1,390 @@
/* Global styles */
:root {
--background: #1e1e2e;
--background-light: #fcebf3;
--button: #78c2ad;
--text: #eff1f5;
--text-light: #1e1e2e;
--text-link: #78c2ad;
--text-link-light: #375a7f;
--underline: #375a7f;
--header: 'Pacifico', cursive;
--body: 'Josefin Sans', sans-serif;
}
html {
background-color: var(--background);
color: var(--text);
}
body {
max-width: 540px;
margin: 0 auto;
text-align: justify;
}
h1,
h2 {
font-family: var(--header);
font-weight: normal;
}
h1 {
font-size: 2.6rem;
margin: 0;
margin-bottom: 5px;
}
h2 {
text-decoration: underline var(--underline);
text-decoration-thickness: 4px;
text-underline-offset: 6px;
font-size: 2.1rem;
margin-bottom: 0;
}
p {
font-family: var(--body);
font-size: 1.1rem;
hyphens: auto;
line-height: 1.5;
text-indent: 1rem;
}
small,
li {
font-family: var(--body);
}
/* Navigation Bar */
.navbar {
display: grid;
grid-template-columns: 1fr 1fr;
margin-top: 0.8rem;
}
.navbar__title {
font-family: var(--header);
font-size: 1.3rem;
}
.navbar__menu {
font-family: var(--body);
font-size: 1.1rem;
text-align: right;
}
.navbar__title>a,
.navbar__menu>a {
color: var(--text);
text-decoration: none;
}
.navbar__menu>a {
margin: 0 10px;
}
.navbar__title>a:hover,
.navbar__menu>a:hover,
.navbar__menu>a:focus {
text-decoration: underline var(--underline);
text-decoration-thickness: 2px;
text-underline-offset: 6px;
}
/* Gifs */
.gif {
margin: 20px 0 0 0;
text-align: center;
}
/* Badge */
.badge {
background-color: #584966;
color: var(--text);
border-radius: 6px;
font-family: var(--body);
font-weight: 600;
font-size: .85rem;
padding: 0.3em 0.6em 0.2em;
}
.badge__list {
text-indent: 1rem;
list-style: none;
padding: 0;
}
.badge__list>li {
margin-top: 10px;
}
.badge--item {
border-radius: 4px;
font-size: 0.7rem;
margin-right: 5px;
padding: 0.5em 0.3em 0.3em 0.3em;
}
.badge__link {
color: var(--text-link);
text-decoration: none;
}
.badge__link:hover {
text-decoration: underline;
}
/* Cards */
.card {
list-style: none;
display: flex;
background-size: 400%;
padding: 20px;
}
.card__link {
width: 100%;
text-decoration: none;
line-height: 1.4;
border-radius: 8px;
color: var(--text);
}
.card__img {
border-radius: 16px;
}
.card__title {
margin: 0;
font-family: var(--body);
font-size: 1.5rem;
font-weight: normal;
text-align: center;
}
.card__txt {
font-size: 1rem;
line-height: 1.5;
text-indent: 0;
text-align: center;
margin: 0.5rem 0 0;
}
.card__link:hover {
transform: scale(1.05);
}
.link-card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
gap: 1rem;
padding: 0;
}
/* Button */
.btn {
text-align: center;
background: rgba(0, 0, 0, 0);
border: 2px solid var(--button);
border-radius: 8px;
color: var(--text);
font: 600 16px var(--body);
padding: 8px 16px;
transition: background-color 0.4s, color 0.4s;
}
.btn:hover {
background-color: var(--button);
color: var(--text-light);
cursor: pointer;
}
/* Skills */
.skills {
margin-top: 8px;
}
.skills__img {
margin: 8px 4px;
}
/* Items */
.center {
text-align: center;
}
.pro-img {
border-radius: 16px;
margin-top: 10px;
}
/* Footer */
footer {
margin: 4rem 0;
}
.footer__link {
color: var(--text-link);
text-decoration: none;
}
.footer {
cursor: default;
list-style: none;
padding: 0;
}
.icon__btn {
display: inline-block;
padding: 0 0.5rem;
}
.icon__link {
display: flex;
padding: 10px;
height: 20px;
width: 20px;
border-radius: 50%;
border: 2px solid #fdebf3;
}
.icon__link:hover {
border-color: var(--text-link);
}
.git-icon {
content: url("/.netlify/images?url=/images/github-mark-white.svg");
}
.mail-icon {
content: url("/.netlify/images?url=/images/mail-white.svg");
}
.linked-in {
content: url("/.netlify/images?url=/images/li-in-white.png");
}
/* 404 */
.lost {
margin-top: 30%;
text-align: center;
}
.lost__header {
margin: 0 auto;
text-align: center;
border: 0;
font-size: 7.5rem;
letter-spacing: 10px;
}
.lost__body {
color: #5C5B77;
}
.lost__link {
color: var(--text-link);
text-decoration: none;
}
/* Theme Icon */
html.light {
background-color: var(--background-light);
color: var(--text-light);
}
.light .navbar__title>a {
color: var(--text-light);
}
.light .navbar__menu>a {
color: var(--text-light);
}
.light .card__title {
color: var(--text-light);
}
.light .card__txt {
color: var(--text-light);
}
.light .btn {
color: var(--text-light);
}
.light .btn:hover {
color: var(--text);
}
.light .icon__link {
border: 2px solid var(--text-light);
}
.light .icon__link:hover {
border-color: var(--text-link);
}
.light .git-icon {
content: url("/.netlify/images?url=/images/github-mark.svg");
}
.light .mail-icon {
content: url("/.netlify/images?url=/images/mail.svg");
}
.light .linked-in {
content: url("/.netlify/images?url=/images/li-in.png");
}
.light .badge__link {
color: var(--text-link-light);
}
.light .footer__link {
color: var(--text-link-light);
}
.light .icon__link:hover {
border-color: var(--text-link-light);
}
/* Media Query */
@media only screen and (max-width: 600px) {
body {
width: 350px;
}
.navbar {
display: initial;
}
.navbar__title {
text-align: center;
}
.navbar__menu {
text-align: center;
}
.navbar__menu>a {
margin: 0 11px;
}
.skills__img {
height: 30px;
width: 30px;
margin: 8px 1px;
}
.badge__list {
font-size: 0.9rem;
text-indent: 0;
}
.pro-img {
height: auto;
width: 350px;
}
}

9
tailwind.config.cjs Normal file
View File

@@ -0,0 +1,9 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', '*.{js,ts,jsx,tsx,mdx}'],
darkMode: ['class', '[data-theme="dark"]'],
theme: {
extend: {},
},
plugins: [require('@tailwindcss/typography')],
};

28
tsconfig.json Normal file
View File

@@ -0,0 +1,28 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"target": "ES6",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,357 @@
name: release-image
on:
push:
branches:
- release
workflow_dispatch:
inputs:
directus-release:
description: 'A parameter passed via API'
required: true
type: boolean
default: false
jobs:
build:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: release
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24.14.1
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Install Dependencies
run: bun install --frozen-lockfile
- name: Cache Astro Build
uses: actions/cache@v5
with:
path: |
.astro
node_modules/.vite
key: ${{ runner.os }}-astro-${{ hashFiles('**/*.astro', 'astro.config.mjs') }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || 'static' }}
restore-keys: |
${{ runner.os }}-astro-${{ hashFiles('**/*.astro', 'astro.config.mjs') }}-
${{ runner.os }}-astro-
- name: Lint Code
run: bun run lint
- name: Build Project
run: bun run build
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Test Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'During release tests failed for building Site Profile'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yaml", "clear": true}]'
image: true
guarddog:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: release
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12.13'
- name: Install GuardDog
run: |
python3 -m pip install --upgrade pip
python3 -m pip install guarddog
- name: Run GuardDog
run: |
guarddog npm scan ./
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Security Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'During release guarddog scan failed for Site Profile'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yaml", "clear": true}]'
image: true
semantic-release:
needs: [ build, guarddog ]
runs-on: ubuntu-js
if: |
github.event_name != 'workflow_dispatch' ||
inputs['directus-release'] == 'true'
outputs:
new-release-published: ${{ steps.semantic.outputs.new-release-published }}
new-release-version: ${{ steps.semantic.outputs.new-release-version }}
new-release-git-tag: ${{ steps.semantic.outputs.new-release-git-tag }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.BOT_TOKEN }}
- name: Prepare Content Patch
if: inputs['directus-release'] == 'true'
run: |
git config user.name "gitea-bot"
git config user.email "gitea-bot@alexlebens.net"
git commit --allow-empty -m "fix(content): directus published update [skip ci]"
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24.14.1
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Install Dependencies
run: bun install --frozen-lockfile
- name: Run Semantic Release
id: semantic
env:
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
NODE_PATH: ${{ github.workspace }}/node_modules
run: |
bun run semantic-release
release-harbor:
runs-on: ubuntu-js
needs: semantic-release
if: ${{ needs.semantic-release.outputs.new-release-published == 'true' }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: release
- name: Login to Harbor Registry
uses: docker/login-action@v4
with:
registry: ${{ vars.REGISTRY_HOST }}
username: ${{ vars.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_SECRET }}
- name: Login to Docker
uses: docker/login-action@v4
with:
registry: ${{ vars.DH_REGISTRY }}
username: ${{ secrets.DH_USERNAME }}
password: ${{ secrets.DH_TOKEN }}
- name: Create Kubeconfig
run: |
mkdir $HOME/.kube
echo "${{ secrets.KUBECONFIG_BUILDX }}" > $HOME/.kube/config
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v4
with:
driver: kubernetes
driver-opts: |
namespace=gitea
qemu.install=true
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["harbor.alexlebens.net/proxy-hub.docker/"]
- name: Available Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Extract Metadata
id: meta
uses: docker/metadata-action@v6
with:
images: |
${{ vars.REGISTRY_HOST }}/images/site-profile
tags: |
type=ref,event=branch
type=sha,format=long
type=raw,value=latest,enable=${{ needs.semantic-release.outputs.new-release-published == 'true' }}
type=semver,pattern={{version}},value=${{ needs.semantic-release.outputs.new-release-version }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic-release.outputs.new-release-version }}
type=semver,pattern={{major}},value=${{ needs.semantic-release.outputs.new-release-version }}
- name: Build and Push Image
uses: docker/build-push-action@v7
with:
context: .
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
APP_VERSION=${{ needs.semantic-release.outputs.new-release-version }}
COMMIT_SHA=${{ github.sha }}
IS_RELEASE=true
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
- name: ntfy Success
uses: niniyas/ntfy-action@master
if: success()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Success - Site Profile'
priority: 3
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,successfully,completed
details: 'Harbor Image for Site Profile has been released!'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Harbor Image for Site Profile has failed to be released.'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yaml", "clear": true}]'
image: true
release-gitea:
runs-on: ubuntu-js
needs: [ semantic-release, release-harbor ]
if: |
always() &&
needs.semantic-release.outputs.new-release-published == 'true'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: release
- name: Login to Gitea Registry
uses: docker/login-action@v4
with:
registry: ${{ vars.REPOSITORY_HOST }}
username: ${{ gitea.actor }}
password: ${{ secrets.REPOSITORY_TOKEN }}
- name: Login to Docker
uses: docker/login-action@v4
with:
registry: ${{ vars.DH_REGISTRY }}
username: ${{ secrets.DH_USERNAME }}
password: ${{ secrets.DH_TOKEN }}
- name: Create Kubeconfig
run: |
mkdir $HOME/.kube
echo "${{ secrets.KUBECONFIG_BUILDX }}" > $HOME/.kube/config
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v4
with:
driver: kubernetes
driver-opts: |
namespace=gitea
qemu.install=true
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["harbor.alexlebens.net/proxy-hub.docker/"]
- name: Available Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Extract Metadata
id: meta
uses: docker/metadata-action@v6
with:
images: |
${{ vars.REPOSITORY_HOST }}/${{ gitea.repository }}
tags: |
type=ref,event=branch
type=sha,format=long
type=raw,value=latest,enable=${{ needs.semantic-release.outputs.new-release-published == 'true' }}
type=semver,pattern={{version}},value=${{ needs.semantic-release.outputs.new-release-version }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic-release.outputs.new-release-version }}
type=semver,pattern={{major}},value=${{ needs.semantic-release.outputs.new-release-version }}
- name: Build and Push Image
uses: docker/build-push-action@v7
with:
context: .
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
APP_VERSION=${{ needs.semantic-release.outputs.new-release-version }}
COMMIT_SHA=${{ github.sha }}
IS_RELEASE=true
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
- name: ntfy Success
uses: niniyas/ntfy-action@master
if: success()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Success - Site Profile'
priority: 3
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,successfully,completed
details: 'Gitea Image for Site Profile has been released!'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Gitea Image for Site Profile has failed to be released.'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yaml", "clear": true}]'
image: true

30
workflows/renovate.yaml Normal file
View File

@@ -0,0 +1,30 @@
name: renovate
on:
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
jobs:
renovate:
runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:43
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Renovate
run: renovate
env:
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: ${{ vars.INSTANCE_URL }}
RENOVATE_REPOSITORIES: alexlebens/site-profile
RENOVATE_GIT_AUTHOR: Renovate Bot <renovate-bot@alexlebens.net>
RENOVATE_REDIS_URL: ${{ vars.RENOVATE_REDIS_URL }}
LOG_LEVEL: debug
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_GIT_PRIVATE_KEY: ${{ secrets.RENOVATE_GIT_PRIVATE_KEY }}
RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
RENOVATE_REGISTRY_ALIASES: '{"dhi.io": "dhi.io"}'
RENOVATE_HOST_RULES: '[{"matchHost":"dhi.io","hostType":"docker","username":"${{ secrets.RENOVATE_DHI_USER }}","password":"${{ secrets.RENOVATE_DHI_TOKEN }}"}]'

99
workflows/test-build.yaml Normal file
View File

@@ -0,0 +1,99 @@
name: test-build
on:
push:
branches:
- main
paths-ignore:
- '.gitea/workflows/**'
- '**.md'
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24.14.1
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Install Dependencies
run: bun install --frozen-lockfile
- name: Cache Astro Build Cache
uses: actions/cache@v5
with:
path: |
.astro
node_modules/.vite
key: ${{ runner.os }}-astro-${{ hashFiles('**/*.astro', 'astro.config.mjs') }}
restore-keys: |
${{ runner.os }}-astro-
- name: Lint Code
run: bun run lint
- name: Build Project
run: bun run build
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Test Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Tests have failed for building Site Profile'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=test-build.yaml", "clear": true}]'
image: true
guarddog:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12.13'
- name: Install GuardDog
run: |
python3 -m pip install --upgrade pip
python3 -m pip install guarddog
- name: Run GuardDog
run: |
guarddog npm scan ./
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Security Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Guarddog scan failed for Site Profile'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=test-build.yaml", "clear": true}]'
image: true