Compare commits
1 Commits
0.9.0
...
7b925b4718
Author | SHA1 | Date | |
---|---|---|---|
7b925b4718
|
@@ -1,5 +1,3 @@
|
||||
.DS_Store
|
||||
.astro
|
||||
.vscode
|
||||
node_modules
|
||||
dist
|
||||
dist
|
@@ -1,40 +0,0 @@
|
||||
name: process-repository
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "@daily"
|
||||
|
||||
jobs:
|
||||
process-repository:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Python Script
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: alexlebens/workflow-scripts
|
||||
ref: main
|
||||
token: ${{ secrets.BOT_TOKEN }}
|
||||
path: workflow-scripts
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install requests immutabledict
|
||||
|
||||
- name: Run Script
|
||||
env:
|
||||
INSTANCE_URL: ${{ vars.INSTANCE_URL }}
|
||||
OWNER: ${{ gitea.owner }}
|
||||
REPOSITORY: ${{ gitea.repository }}
|
||||
TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
LOG_LEVEL: DEBUG
|
||||
ISSUE_STALE_DAYS: 3
|
||||
ISSUE_STALE_TAG: 23
|
||||
ISSUE_EXCLUDE_TAG: 17
|
||||
PULL_REQUEST_STALE_DAYS: 3
|
||||
PULL_REQUEST_STALE_TAG: 23
|
||||
PULL_REQUEST_REQUIRED_TAG: 22
|
||||
run: python ./workflow-scripts/process-repository.py
|
67
.gitea/workflows/release-image-gitea.yml
Normal file
67
.gitea/workflows/release-image-gitea.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
name: release-image-gitea
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 0.*
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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@v3
|
||||
with:
|
||||
driver: kubernetes
|
||||
driver-opts: |
|
||||
namespace=gitea
|
||||
qemu.install=true
|
||||
|
||||
- name: Available Platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
|
||||
- name: Login to Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.REPOSITORY_HOST }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.REPOSITORY_TOKEN }}
|
||||
|
||||
- name: Extract Metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
images: ${{ vars.REPOSITORY_HOST }}/${{ gitea.repository }}
|
||||
|
||||
- name: Build and Push Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
file: ./Dockerfile
|
||||
|
||||
- name: Actions Ntfy
|
||||
run: |
|
||||
curl \
|
||||
-H "Authorization: Bearer ${{ secrets.NTFY_CRED }}" \
|
||||
-H "Title: Site-Profile Image Released to Gitea: ${{ steps.meta.outputs.tags }}" \
|
||||
-H "Content-Type: text/plain" \
|
||||
-d $'Repo: ${{ gitea.repository }}\nCommit: ${{ gitea.sha }}\nRef: ${{ gitea.ref }}\nStatus: ${{ job.status}}' \
|
||||
${{ secrets.NTFY_URL }}
|
67
.gitea/workflows/release-image-harbor.yml
Normal file
67
.gitea/workflows/release-image-harbor.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
name: release-image-harbor
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 0.*
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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@v3
|
||||
with:
|
||||
driver: kubernetes
|
||||
driver-opts: |
|
||||
namespace=gitea
|
||||
qemu.install=true
|
||||
|
||||
- name: Available Platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
|
||||
- name: Login to Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.REGISTRY_HOST }}
|
||||
username: ${{ vars.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_SECRET }}
|
||||
|
||||
- name: Extract Metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
images: ${{ vars.REGISTRY_HOST }}/images/site-profile
|
||||
|
||||
- name: Build and Push Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
file: ./Dockerfile
|
||||
|
||||
- name: Actions Ntfy
|
||||
run: |
|
||||
curl \
|
||||
-H "Authorization: Bearer ${{ secrets.NTFY_CRED }}" \
|
||||
-H "Title: Site-Profile Image Released to Gitea: ${{ steps.meta.outputs.tags }}" \
|
||||
-H "Content-Type: text/plain" \
|
||||
-d $'Repo: ${{ gitea.repository }}\nCommit: ${{ gitea.sha }}\nRef: ${{ gitea.ref }}\nStatus: ${{ job.status}}' \
|
||||
${{ secrets.NTFY_URL }}
|
@@ -1,98 +0,0 @@
|
||||
name: release-image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 0.*
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.REPOSITORY_HOST }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.REPOSITORY_TOKEN }}
|
||||
|
||||
- name: Login to Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.REGISTRY_HOST }}
|
||||
username: ${{ vars.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_SECRET }}
|
||||
|
||||
- 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@v3
|
||||
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@v5
|
||||
with:
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
images: |
|
||||
${{ vars.REPOSITORY_HOST }}/${{ gitea.repository }}
|
||||
${{ vars.REGISTRY_HOST }}/images/site-profile
|
||||
|
||||
- name: Build and Push Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
file: ./Dockerfile
|
||||
|
||||
- name: ntfy Success
|
||||
uses: niniyas/ntfy-action@master
|
||||
if: success()
|
||||
with:
|
||||
url: '${{ secrets.NTFY_URL }}'
|
||||
topic: '${{ secrets.NTFY_TOPIC }}'
|
||||
title: 'Gitea Action'
|
||||
priority: 3
|
||||
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
|
||||
tags: action,successfully,completed
|
||||
details: 'Site Profile build workflow has successfully completed!'
|
||||
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: 'Gitea Action'
|
||||
priority: 4
|
||||
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
|
||||
tags: action,failed
|
||||
details: 'Site Profile build workflow has failed!'
|
||||
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.yml", "clear": true}]'
|
||||
image: true
|
@@ -13,20 +13,18 @@ on:
|
||||
jobs:
|
||||
renovate:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/renovatebot/renovate:41
|
||||
container: ghcr.io/renovatebot/renovate:40
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Renovate
|
||||
run: renovate
|
||||
- uses: actions/checkout@v4
|
||||
- run: renovate
|
||||
env:
|
||||
RENOVATE_PLATFORM: gitea
|
||||
RENOVATE_ENDPOINT: ${{ vars.INSTANCE_URL }}
|
||||
RENOVATE_REPOSITORIES: alexlebens/site-profile
|
||||
RENOVATE_AUTODISCOVER: true
|
||||
RENOVATE_ONBOARDING: true
|
||||
RENOVATE_ENDPOINT: http://gitea-http.gitea:3000
|
||||
RENOVATE_GIT_AUTHOR: Renovate Bot <renovate-bot@alexlebens.net>
|
||||
LOG_LEVEL: info
|
||||
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_REDIS_URL: ${{ vars.RENOVATE_REDIS_URL }}
|
||||
RENOVATE_REDIS_URL: redis://gitea-renovate-valkey-primary.gitea:6379
|
||||
|
@@ -1,37 +0,0 @@
|
||||
name: test-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.x
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.17.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint Code
|
||||
run: pnpm lint
|
||||
|
||||
- name: Build Project
|
||||
run: pnpm build
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,3 +24,4 @@ pnpm-debug.log*
|
||||
# ide
|
||||
.vscode/
|
||||
site-profile.code-workspace
|
||||
.pre-commit-config.yaml
|
||||
|
17
.prettierrc
Normal file
17
.prettierrc
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
4
.vscode/extensions.json
vendored
Normal file
4
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
11
Dockerfile
11
Dockerfile
@@ -1,8 +1,7 @@
|
||||
ARG REGISTRY=docker.io
|
||||
FROM ${REGISTRY}/node:22.17.0-alpine3.22 AS base
|
||||
FROM node:22.16.0-alpine3.22 AS base
|
||||
|
||||
LABEL version="0.9.0"
|
||||
LABEL description="Astro based personal website"
|
||||
LABEL version="0.7.0"
|
||||
LABEL description="Astro based website to use as a personal site"
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
@@ -21,7 +20,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
FROM build-deps AS build
|
||||
COPY . .
|
||||
RUN pnpm run build
|
||||
RUN pnpm prune --prod
|
||||
|
||||
FROM base AS runtime
|
||||
COPY --from=prod-deps /app/node_modules /app/node_modules
|
||||
@@ -31,6 +29,5 @@ ENV HOST=0.0.0.0
|
||||
ENV SITE_URL=https://www.alexlebens.dev
|
||||
ENV DIRECTUS_URL=https://directus.alexlebens.dev
|
||||
ENV PORT=4321
|
||||
|
||||
EXPOSE $PORT
|
||||
CMD ["node", "./dist/server/entry.mjs"]
|
||||
CMD node ./dist/server/entry.mjs
|
@@ -2,8 +2,6 @@
|
||||
|
||||
Copyright (c) 2025 Lê Vĩnh Khang
|
||||
|
||||
Copyright (c) 2025 Alex Lebens
|
||||
|
||||
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
|
||||
|
14
README.md
14
README.md
@@ -21,7 +21,7 @@ Personal site used for information about myself and blog.
|
||||
|
||||
### Requirements
|
||||
|
||||
- Node.js 22+ and pnpm
|
||||
- Node.js 16+ and pnpm/yarn
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -30,18 +30,22 @@ Personal site used for information about myself and blog.
|
||||
git clone https://gitea.alexlebens.dev/alexlebens/site-profile
|
||||
|
||||
# Navigate to project directory
|
||||
cd site-profile
|
||||
cd astro-blog
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Create .env file from template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env with your information
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Start development server
|
||||
pnpm dev
|
||||
pnpm run dev
|
||||
|
||||
# Open browser at http://localhost:4321
|
||||
```
|
||||
@@ -50,10 +54,10 @@ pnpm dev
|
||||
|
||||
```bash
|
||||
# Create production build
|
||||
pnpm build
|
||||
pnpm run build
|
||||
|
||||
# Preview production build
|
||||
pnpm preview
|
||||
pnpm run preview
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
import react from '@astrojs/react';
|
||||
|
||||
import node from '@astrojs/node';
|
||||
|
||||
const getSiteURL = () => {
|
||||
if (process.env.SITE_URL) {
|
||||
return `https://${process.env.SITE_URL}`;
|
||||
@@ -13,17 +11,5 @@ const getSiteURL = () => {
|
||||
|
||||
export default defineConfig({
|
||||
site: getSiteURL(),
|
||||
integrations: [tailwindcss(), react()],
|
||||
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
|
||||
adapter: node({
|
||||
mode: 'standalone',
|
||||
}),
|
||||
integrations: [tailwind(), react()],
|
||||
});
|
||||
|
@@ -1,11 +0,0 @@
|
||||
import eslintPluginAstro from 'eslint-plugin-astro';
|
||||
import eslintConfigPrettier from "eslint-config-prettier/flat";
|
||||
|
||||
export default [
|
||||
...eslintPluginAstro.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
{
|
||||
rules: {
|
||||
}
|
||||
}
|
||||
];
|
@@ -22,7 +22,6 @@ type About = {
|
||||
type Links = {
|
||||
github: string;
|
||||
linkedin: string;
|
||||
gitea: string;
|
||||
};
|
||||
|
||||
type Skill = {
|
||||
|
26
package.json
26
package.json
@@ -1,15 +1,13 @@
|
||||
{
|
||||
"name": "site-profile",
|
||||
"type": "module",
|
||||
"version": "0.9.0",
|
||||
"version": "0.7.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\"",
|
||||
"lint": "eslint \"src/**/*.{js,ts,jsx,tsx,astro}\"",
|
||||
"lint:fix": "eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\"",
|
||||
"format": "prettier . --write",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -17,27 +15,23 @@
|
||||
"@astrojs/node": "^9.2.2",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
"@astrojs/rss": "^4.0.12",
|
||||
"@directus/sdk": "^20.0.0",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@tailwindcss/vite": "^4.1.8",
|
||||
"astro": "^5.10.1",
|
||||
"@astrojs/sitemap": "^3.4.1",
|
||||
"@astrojs/tailwind": "^6.0.2",
|
||||
"@directus/sdk": "^19.1.0",
|
||||
"astro": "^5.9.1",
|
||||
"framer-motion": "^12.16.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hotkeys-hook": "^5.1.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"reading-time": "^1.5.0",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"tailwindcss": "^4.1.8"
|
||||
"tailwindcss": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@typescript-eslint/parser": "8.36.0",
|
||||
"eslint": "9.31.0",
|
||||
"eslint-config-prettier": "10.1.5",
|
||||
"eslint-plugin-astro": "1.3.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"prettier-plugin-tailwindcss": "^0.6.12",
|
||||
"typescript-eslint": "8.36.0"
|
||||
"prettier-plugin-astro": "^0.14.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
2179
pnpm-lock.yaml
generated
2179
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,13 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
'postcss-preset-env': {
|
||||
features: {
|
||||
'nesting-rules': false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -1,23 +0,0 @@
|
||||
/** @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;
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
9
public/favicon.svg
Normal file
9
public/favicon.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path fill="#000" 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>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 713 B |
@@ -1,40 +1,10 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
"mergeConfidence:all-badges",
|
||||
":rebaseStalePrs"
|
||||
],
|
||||
"timezone": "US/Central",
|
||||
"labels": [],
|
||||
"prHourlyLimit": 0,
|
||||
"prConcurrentLimit": 0,
|
||||
"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"
|
||||
}
|
||||
]
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended", "mergeConfidence:all-badges", ":rebaseStalePrs"],
|
||||
"timezone": "US/Central",
|
||||
"schedule": ["* */1 * * *"],
|
||||
"labels": [],
|
||||
"prHourlyLimit": 0,
|
||||
"prConcurrentLimit": 0,
|
||||
"packageRules": []
|
||||
}
|
||||
|
@@ -5,17 +5,17 @@
|
||||
<div class="theme-transition-all fixed inset-0 -z-10 overflow-hidden">
|
||||
<!-- Dot pattern background -->
|
||||
<div
|
||||
class="bg-grid-pattern theme-transition-bg absolute inset-0 [mask-image:radial-gradient(white,transparent_85%)] bg-[center_top_-1px]"
|
||||
class="bg-grid-pattern theme-transition-bg absolute inset-0 bg-[center_top_-1px] [mask-image:radial-gradient(white,transparent_85%)]"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Ambient glow effects -->
|
||||
<div
|
||||
class="animate-glow theme-transition-bg absolute top-1/4 left-1/4 h-96 w-96 -translate-x-1/2 -translate-y-1/2 rounded-full bg-zinc-400/20 opacity-50 blur-3xl dark:bg-zinc-500/20"
|
||||
class="animate-glow theme-transition-bg absolute left-1/4 top-1/4 h-96 w-96 -translate-x-1/2 -translate-y-1/2 rounded-full bg-zinc-400/20 opacity-50 blur-3xl dark:bg-zinc-500/20"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-glow animation-delay-1000 theme-transition-bg absolute right-1/4 bottom-1/3 h-64 w-64 translate-x-1/2 translate-y-1/2 rounded-full bg-zinc-300/20 opacity-40 blur-3xl dark:bg-zinc-600/20"
|
||||
class="animate-glow animation-delay-1000 theme-transition-bg absolute bottom-1/3 right-1/4 h-64 w-64 translate-x-1/2 translate-y-1/2 rounded-full bg-zinc-300/20 opacity-40 blur-3xl dark:bg-zinc-600/20"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
@@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
import * as FaIcons from 'react-icons/fa';
|
||||
import * as MdIcons from 'react-icons/md';
|
||||
import * as AiIcons from 'react-icons/ai';
|
||||
import * as GiIcons from 'react-icons/gi';
|
||||
import * as IoIcons from 'react-icons/io';
|
||||
import * as CiIcons from "react-icons/ci";
|
||||
import * as FiIcons from "react-icons/fi";
|
||||
import * as LuIcons from "react-icons/lu";
|
||||
import * as SiIcons from 'react-icons/si';
|
||||
|
||||
const iconSets = {
|
||||
fa: FaIcons,
|
||||
md: MdIcons,
|
||||
ai: AiIcons,
|
||||
gi: GiIcons,
|
||||
io: IoIcons,
|
||||
ci: CiIcons,
|
||||
fi: FiIcons,
|
||||
lu: LuIcons,
|
||||
si: SiIcons,
|
||||
};
|
||||
|
||||
const DynamicIcon = ({ name, set = 'fa', size = 20, color = 'currentColor', className = '' }: {name: string, set: string, size: number, color: string, className: string }) => {
|
||||
let IconComponent = FaIcons.FaAlignCenter;
|
||||
|
||||
if (name.startsWith("Fa")) {
|
||||
IconComponent = iconSets["fa"][name]
|
||||
} else if (name.startsWith("Si")) {
|
||||
IconComponent = iconSets["si"][name]
|
||||
} else {
|
||||
IconComponent = iconSets[set][name];
|
||||
}
|
||||
|
||||
return <IconComponent size={size} color={color} className={className} />;
|
||||
};
|
||||
|
||||
export default DynamicIcon;
|
@@ -20,11 +20,6 @@ const socialLinks = [
|
||||
href: links.github,
|
||||
icon: `<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path>`,
|
||||
},
|
||||
{
|
||||
name: 'Gitea',
|
||||
href: links.gitea,
|
||||
icon: `<path d="M7 5C7 3.89543 7.89543 3 9 3C10.1046 3 11 3.89543 11 5C11 5.34168 10.9143 5.66336 10.7633 5.9447H11.3438C13.5529 5.9447 15.3438 7.73556 15.3438 9.9447V11.2244C15.9301 11.5731 16.323 12.213 16.323 12.9447C16.323 14.0493 15.4276 14.9447 14.323 14.9447C13.2184 14.9447 12.323 14.0493 12.323 12.9447C12.323 12.1959 12.7345 11.5432 13.3438 11.2004V9.9447C13.3438 8.84013 12.4483 7.9447 11.3438 7.9447H10V17.2676C10.5978 17.6134 11 18.2597 11 19C11 20.1046 10.1046 21 9 21C7.89543 21 7 20.1046 7 19C7 18.2597 7.4022 17.6134 8 17.2676V6.73244C7.4022 6.38663 7 5.74028 7 5Z" fill="currentColor"/>`,
|
||||
},
|
||||
{
|
||||
name: 'LinkedIn',
|
||||
href: links.linkedin,
|
||||
@@ -38,7 +33,7 @@ const socialLinks = [
|
||||
>
|
||||
<div class="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
<div
|
||||
class="theme-transition-all animate-float-slow absolute -top-40 -right-40 h-80 w-80 rounded-full bg-zinc-100 opacity-50 blur-3xl dark:bg-zinc-800/30"
|
||||
class="theme-transition-all animate-float-slow absolute -right-40 -top-40 h-80 w-80 rounded-full bg-zinc-100 opacity-50 blur-3xl dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
@@ -46,12 +41,12 @@ const socialLinks = [
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="theme-transition-all animate-float-slow animation-delay-1000 absolute top-20 left-1/4 h-40 w-40 rounded-full bg-zinc-200/50 opacity-30 blur-2xl dark:bg-zinc-700/20"
|
||||
class="theme-transition-all animate-float-slow animation-delay-1000 absolute left-1/4 top-20 h-40 w-40 rounded-full bg-zinc-200/50 opacity-30 blur-2xl dark:bg-zinc-700/20"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative px-4 pt-16 pb-12 sm:px-6">
|
||||
<div class="relative px-4 pb-12 pt-16 sm:px-6">
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<!-- Main footer content -->
|
||||
<div class="grid grid-cols-1 gap-10 md:grid-cols-12">
|
||||
@@ -92,7 +87,7 @@ const socialLinks = [
|
||||
href={social.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="hover group relative flex h-10 w-10 transform items-center justify-center rounded-full bg-zinc-100 text-zinc-500 transition-all duration-300 hover:-translate-y-1 hover:text-zinc-900 hover:ring-2 hover:ring-zinc-300 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-100 dark:hover:ring-zinc-700"
|
||||
class="group relative flex h-10 w-10 transform items-center justify-center rounded-full bg-zinc-100 text-zinc-500 transition-all duration-300 hover:-translate-y-1 hover:text-zinc-900 hover:ring-2 hover:ring-zinc-300 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-100 dark:hover:ring-zinc-700"
|
||||
aria-label={social.name}
|
||||
>
|
||||
<span class="absolute inset-0 rounded-full bg-gradient-to-br from-zinc-200 to-zinc-300 opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:from-zinc-700 dark:to-zinc-600" />
|
||||
@@ -113,7 +108,7 @@ const socialLinks = [
|
||||
<!-- Quick links -->
|
||||
<div class="col-span-1 md:col-span-3">
|
||||
<h3
|
||||
class="theme-transition-color relative inline-block pb-2 text-sm font-semibold tracking-wider text-zinc-900 uppercase after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:bg-zinc-300 after:content-[''] dark:text-zinc-100 dark:after:bg-zinc-700"
|
||||
class="theme-transition-color relative inline-block pb-2 text-sm font-semibold uppercase tracking-wider text-zinc-900 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:bg-zinc-300 after:content-[''] dark:text-zinc-100 dark:after:bg-zinc-700"
|
||||
>
|
||||
Navigation
|
||||
</h3>
|
||||
@@ -135,115 +130,115 @@ const socialLinks = [
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom section -->
|
||||
<div class="theme-transition-all mt-12 border-t border-zinc-200 pt-8 dark:border-zinc-800">
|
||||
<div class="flex flex-col items-center justify-between gap-4 md:flex-row">
|
||||
<p class="theme-transition-color text-sm text-zinc-600 dark:text-zinc-400">
|
||||
© {currentYear} All rights reserved.
|
||||
</p>
|
||||
<!-- Bottom section -->
|
||||
<div class="theme-transition-all mt-12 border-t border-zinc-200 pt-8 dark:border-zinc-800">
|
||||
<div class="flex flex-col items-center justify-between gap-4 md:flex-row">
|
||||
<p class="theme-transition-color text-sm text-zinc-600 dark:text-zinc-400">
|
||||
© {currentYear} All rights reserved.
|
||||
</p>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="theme-transition-color text-xs text-zinc-500 dark:text-zinc-400"
|
||||
>Built with</span
|
||||
>
|
||||
<a
|
||||
href="https://astro.build"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group inline-flex items-center text-xs text-zinc-600 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
|
||||
>
|
||||
<svg
|
||||
class="mr-1 h-4 w-4 text-[#FF5D01] group-hover:animate-pulse"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="theme-transition-color text-xs text-zinc-500 dark:text-zinc-400"
|
||||
>Built with</span
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.833 22.958c.622-1.185 1.832-1.918 3.18-1.918 2.292 0 4.145 1.86 4.145 4.153 0 1.34-.626 2.54-1.601 3.303 1.223-1.299 1.97-3.048 1.97-4.971 0-3.994-3.243-7.233-7.242-7.233-2.818 0-5.26 1.6-6.469 3.933.78-2.912 3.428-5.06 6.577-5.06 3.75 0 6.79 3.035 6.79 6.78 0 2.606-1.468 4.868-3.616 6.002a4.163 4.163 0 0 0 2.285-3.724c0-2.293-1.853-4.153-4.145-4.153-1.348 0-2.558.733-3.18 1.918l1.306-3.03Z"
|
||||
fill="currentColor"></path>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M22.155 12.056c-.622 1.185-1.832 1.918-3.18 1.918-2.292 0-4.145-1.86-4.145-4.153 0-1.34.626-2.54 1.601-3.303-1.223 1.299-1.97 3.048-1.97 4.971 0 3.994 3.243 7.233 7.242 7.233 2.818 0 5.26-1.6 6.469-3.933-.78 2.912-3.428 5.06-6.577 5.06-3.75 0-6.79-3.035-6.79-6.78 0-2.606 1.468-4.868 3.616-6.002a4.163 4.163 0 0 0-2.285 3.724c0 2.293 1.853 4.153 4.145 4.153 1.348 0 2.558-.733 3.18-1.918l-1.306 3.03Z"
|
||||
fill="currentColor"></path>
|
||||
</svg>
|
||||
<span class="relative">
|
||||
Astro
|
||||
<span
|
||||
class="absolute bottom-0 left-0 h-0.5 w-0 bg-[#FF5D01] transition-all duration-300 group-hover:w-full"
|
||||
></span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://astro.build"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group inline-flex items-center text-xs text-zinc-600 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
|
||||
>
|
||||
<svg
|
||||
class="mr-1 h-4 w-4 text-[#FF5D01] group-hover:animate-pulse"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.833 22.958c.622-1.185 1.832-1.918 3.18-1.918 2.292 0 4.145 1.86 4.145 4.153 0 1.34-.626 2.54-1.601 3.303 1.223-1.299 1.97-3.048 1.97-4.971 0-3.994-3.243-7.233-7.242-7.233-2.818 0-5.26 1.6-6.469 3.933.78-2.912 3.428-5.06 6.577-5.06 3.75 0 6.79 3.035 6.79 6.78 0 2.606-1.468 4.868-3.616 6.002a4.163 4.163 0 0 0 2.285-3.724c0-2.293-1.853-4.153-4.145-4.153-1.348 0-2.558.733-3.18 1.918l1.306-3.03Z"
|
||||
fill="currentColor"></path>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M22.155 12.056c-.622 1.185-1.832 1.918-3.18 1.918-2.292 0-4.145-1.86-4.145-4.153 0-1.34.626-2.54 1.601-3.303-1.223 1.299-1.97 3.048-1.97 4.971 0 3.994 3.243 7.233 7.242 7.233 2.818 0 5.26-1.6 6.469-3.933-.78 2.912-3.428 5.06-6.577 5.06-3.75 0-6.79-3.035-6.79-6.78 0-2.606 1.468-4.868 3.616-6.002a4.163 4.163 0 0 0-2.285 3.724c0 2.293 1.853 4.153 4.145 4.153 1.348 0 2.558-.733 3.18-1.918l-1.306 3.03Z"
|
||||
fill="currentColor"></path>
|
||||
</svg>
|
||||
<span class="relative">
|
||||
Astro
|
||||
<span
|
||||
class="absolute bottom-0 left-0 h-0.5 w-0 bg-[#FF5D01] transition-all duration-300 group-hover:w-full"
|
||||
></span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.theme-transition-all {
|
||||
transition-property: background-color, border-color, color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.theme-transition-color {
|
||||
transition-property: color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.theme-transition-bg {
|
||||
transition-property: background-color;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float-slow {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-10px) translateX(10px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px) translateX(-5px);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(10px) translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
.animate-float-slow {
|
||||
animation: float-slow 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animation-delay-1000 {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
</style>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
.theme-transition-all {
|
||||
transition-property: background-color, border-color, color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.theme-transition-color {
|
||||
transition-property: color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.theme-transition-bg {
|
||||
transition-property: background-color;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float-slow {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-10px) translateX(10px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px) translateX(-5px);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(10px) translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
.animate-float-slow {
|
||||
animation: float-slow 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animation-delay-1000 {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
</style>
|
||||
|
@@ -19,7 +19,7 @@ const currentPath = pathname.slice(1);
|
||||
---
|
||||
|
||||
<header
|
||||
class="fixed top-0 right-0 left-0 z-40 border-b border-zinc-100 bg-white py-4 dark:border-zinc-800 dark:bg-zinc-900"
|
||||
class="fixed left-0 right-0 top-0 z-40 border-b border-zinc-100 bg-white py-4 dark:border-zinc-800 dark:bg-zinc-900"
|
||||
>
|
||||
<div class="mx-auto flex max-w-3xl items-center justify-between px-4">
|
||||
<!-- Logo -->
|
||||
@@ -72,7 +72,7 @@ const currentPath = pathname.slice(1);
|
||||
class="pointer-events-none fixed inset-0 z-50 flex flex-col bg-white opacity-0 transition-all duration-300 ease-in-out dark:bg-zinc-900"
|
||||
>
|
||||
<div class="flex items-center justify-between border-b border-zinc-100 p-4 dark:border-zinc-800">
|
||||
<a href="/" class="text-xl font-bold text-zinc-900 dark:text-white">{global.initals}</a>
|
||||
<a href="/" class="text-xl font-bold text-zinc-900 dark:text-white">JD</a>
|
||||
<button
|
||||
id="close-menu-button"
|
||||
class="rounded-md p-2 text-zinc-900 transition-colors hover:bg-zinc-100 dark:text-white dark:hover:bg-zinc-800"
|
||||
@@ -200,9 +200,9 @@ const currentPath = pathname.slice(1);
|
||||
|
||||
// Add shadow on scroll
|
||||
if (currentScrollY > 10) {
|
||||
header.classList.add('shadow-xs');
|
||||
header.classList.add('shadow-sm');
|
||||
} else {
|
||||
header.classList.remove('shadow-xs');
|
||||
header.classList.remove('shadow-sm');
|
||||
}
|
||||
|
||||
// Update last scroll position
|
||||
@@ -240,6 +240,6 @@ const currentPath = pathname.slice(1);
|
||||
/* Mobile menu transition */
|
||||
#mobile-menu {
|
||||
transition: opacity 0.3s ease;
|
||||
backdrop-filter: blur-sm(4px);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
</style>
|
||||
|
@@ -17,7 +17,7 @@ const encodedUrl = encodeURIComponent(url);
|
||||
href={`https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="hover rounded-full p-2 text-zinc-500 transition-all duration-300 hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
|
||||
class="rounded-full p-2 text-zinc-500 transition-all duration-300 hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
|
||||
aria-label="Share on Twitter"
|
||||
>
|
||||
<svg
|
||||
@@ -38,7 +38,7 @@ const encodedUrl = encodeURIComponent(url);
|
||||
href={`https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="hover rounded-full p-2 text-zinc-500 transition-all duration-300 hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
|
||||
class="rounded-full p-2 text-zinc-500 transition-all duration-300 hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
|
||||
aria-label="Share on Facebook"
|
||||
>
|
||||
<svg
|
||||
@@ -57,7 +57,7 @@ const encodedUrl = encodeURIComponent(url);
|
||||
href={`https://www.linkedin.com/shareArticle?mini=true&url=${encodedUrl}&title=${encodedTitle}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="hover rounded-full p-2 text-zinc-500 transition-all duration-300 hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
|
||||
class="rounded-full p-2 text-zinc-500 transition-all duration-300 hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
|
||||
aria-label="Share on LinkedIn"
|
||||
>
|
||||
<svg
|
||||
@@ -94,7 +94,7 @@ const encodedUrl = encodeURIComponent(url);
|
||||
>
|
||||
<span
|
||||
id="copy-tooltip"
|
||||
class="absolute -top-8 left-1/2 -translate-x-1/2 transform rounded-sm bg-zinc-800 px-2 py-1 text-xs whitespace-nowrap text-white opacity-0 transition-opacity duration-300 dark:bg-zinc-700"
|
||||
class="absolute -top-8 left-1/2 -translate-x-1/2 transform whitespace-nowrap rounded bg-zinc-800 px-2 py-1 text-xs text-white opacity-0 transition-opacity duration-300 dark:bg-zinc-700"
|
||||
>
|
||||
Copied!
|
||||
</span>
|
||||
|
@@ -5,14 +5,14 @@
|
||||
<button
|
||||
id="theme-toggle"
|
||||
data-theme-toggle
|
||||
class="group relative touch-manipulation overflow-hidden rounded-full p-1.5 transition-all duration-300 hover:bg-zinc-100 focus:ring-2 focus:ring-zinc-300 focus:outline-hidden sm:p-2 dark:hover:bg-zinc-800 dark:focus:ring-zinc-700"
|
||||
class="group relative touch-manipulation overflow-hidden rounded-full p-1.5 transition-all duration-300 hover:bg-zinc-100 focus:outline-none focus:ring-2 focus:ring-zinc-300 dark:hover:bg-zinc-800 dark:focus:ring-zinc-700 sm:p-2"
|
||||
aria-label="Toggle dark mode"
|
||||
>
|
||||
<div class="relative z-10 flex h-5 w-5 items-center justify-center">
|
||||
<!-- Sun icon -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon-light absolute h-5 w-5 scale-100 rotate-0 text-zinc-800 transition-all duration-500 dark:scale-0 dark:-rotate-90 dark:text-zinc-200"
|
||||
class="icon-light absolute h-5 w-5 rotate-0 scale-100 text-zinc-800 transition-all duration-500 dark:-rotate-90 dark:scale-0 dark:text-zinc-200"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
@@ -29,7 +29,7 @@
|
||||
<!-- Moon icon -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon-dark absolute h-5 w-5 scale-0 rotate-90 text-zinc-800 transition-all duration-500 dark:scale-100 dark:rotate-0 dark:text-zinc-200"
|
||||
class="icon-dark absolute h-5 w-5 rotate-90 scale-0 text-zinc-800 transition-all duration-500 dark:rotate-0 dark:scale-100 dark:text-zinc-200"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
@@ -274,12 +274,12 @@
|
||||
}
|
||||
|
||||
#theme-toggle:hover .icon-light:not(.dark .icon-light) {
|
||||
filter: drop-shadow-sm(0 0 2px rgba(251, 191, 36, 0.6));
|
||||
filter: drop-shadow(0 0 2px rgba(251, 191, 36, 0.6));
|
||||
transform: scale(1.1) rotate(15deg);
|
||||
}
|
||||
|
||||
#theme-toggle:hover .icon-dark:not(:not(.dark) .icon-dark) {
|
||||
filter: drop-shadow-sm(0 0 2px rgba(129, 140, 248, 0.6));
|
||||
filter: drop-shadow(0 0 2px rgba(129, 140, 248, 0.6));
|
||||
transform: scale(1.1) rotate(-15deg);
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,6 @@ export interface Props {
|
||||
}
|
||||
---
|
||||
|
||||
<Layout title={global.title} description={global.title}>
|
||||
<Layout title={global.title} description={global.description}>
|
||||
<slot />
|
||||
</Layout>
|
||||
|
@@ -12,7 +12,7 @@ export interface Props {
|
||||
}
|
||||
---
|
||||
|
||||
<Layout title={global.title} description={global.title}>
|
||||
<Layout title={global.title} description={global.description}>
|
||||
<slot />
|
||||
</Layout>
|
||||
|
||||
|
@@ -30,10 +30,10 @@ try {
|
||||
---
|
||||
|
||||
<Layout title={post.title} description={post.description}>
|
||||
<article class="prose prose-zinc dark:prose-invert lg:prose-lg mx-auto max-w-4xl">
|
||||
<article class="prose prose-zinc mx-auto max-w-4xl dark:prose-invert lg:prose-lg">
|
||||
<div class="mb-12">
|
||||
<h1
|
||||
class="mb-4 text-4xl font-bold tracking-tight text-zinc-900 sm:text-5xl dark:text-zinc-100"
|
||||
class="mb-4 text-4xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:text-5xl"
|
||||
>
|
||||
{post.title}
|
||||
</h1>
|
||||
@@ -76,7 +76,7 @@ try {
|
||||
|
||||
{
|
||||
post.updated_date && (
|
||||
<div class="mt-8 text-sm text-zinc-500 italic dark:text-zinc-400">
|
||||
<div class="mt-8 text-sm italic text-zinc-500 dark:text-zinc-400">
|
||||
Last updated on <FormattedDate date={post.updated_date} />
|
||||
</div>
|
||||
)
|
||||
|
@@ -17,7 +17,7 @@ const { title, description } = Astro.props;
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta name="description" content={description} />
|
||||
<title>{title}</title>
|
||||
@@ -42,7 +42,7 @@ const { title, description } = Astro.props;
|
||||
<!-- Background component with dot pattern and ambient glow -->
|
||||
<Background />
|
||||
|
||||
<div class="mx-auto w-full max-w-3xl grow px-4 sm:px-6">
|
||||
<div class="mx-auto w-full max-w-3xl flex-grow px-4 sm:px-6">
|
||||
<Navigation />
|
||||
<main class="py-12">
|
||||
<slot />
|
||||
@@ -281,7 +281,7 @@ const { title, description } = Astro.props;
|
||||
/* Page transition effects */
|
||||
#page-transition {
|
||||
transition: opacity 0.3s ease;
|
||||
backdrop-filter: blur-sm(4px);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
/* Transition spinner animation */
|
||||
|
@@ -1,12 +1,12 @@
|
||||
---
|
||||
import { ClientRouter } from 'astro:transitions';
|
||||
import { ViewTransitions } from 'astro:transitions';
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description}>
|
||||
<ClientRouter fallback="swap" />
|
||||
<ViewTransitions fallback="swap" />
|
||||
|
||||
<div transition:animate="slide">
|
||||
<slot />
|
||||
|
@@ -9,11 +9,11 @@ import Layout from '../layouts/Layout.astro';
|
||||
<!-- Animated background elements -->
|
||||
<div class="absolute inset-0 overflow-hidden">
|
||||
<div
|
||||
class="animate-blob absolute -top-20 -left-20 h-64 w-64 rounded-full bg-zinc-100 opacity-50 blur-3xl dark:bg-zinc-800/50"
|
||||
class="animate-blob absolute -left-20 -top-20 h-64 w-64 rounded-full bg-zinc-100 opacity-50 blur-3xl dark:bg-zinc-800/50"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 absolute top-1/2 right-1/4 h-96 w-96 rounded-full bg-zinc-200 opacity-30 blur-3xl dark:bg-zinc-800/30"
|
||||
class="animate-blob animation-delay-2000 absolute right-1/4 top-1/2 h-96 w-96 rounded-full bg-zinc-200 opacity-30 blur-3xl dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
@@ -26,14 +26,14 @@ import Layout from '../layouts/Layout.astro';
|
||||
<div class="relative z-10 mx-auto max-w-xl">
|
||||
<div class="glitch-wrapper">
|
||||
<h1
|
||||
class="glitch text-9xl leading-none font-bold text-zinc-900 sm:text-[12rem] dark:text-zinc-100"
|
||||
class="glitch text-9xl font-bold leading-none text-zinc-900 dark:text-zinc-100 sm:text-[12rem]"
|
||||
data-text="404"
|
||||
>
|
||||
404
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<h2 class="mt-6 text-2xl font-bold text-zinc-800 sm:text-3xl dark:text-zinc-200">
|
||||
<h2 class="mt-6 text-2xl font-bold text-zinc-800 dark:text-zinc-200 sm:text-3xl">
|
||||
Page Not Found
|
||||
</h2>
|
||||
|
||||
@@ -68,7 +68,7 @@ import Layout from '../layouts/Layout.astro';
|
||||
|
||||
<button
|
||||
id="back-button"
|
||||
class="group inline-flex items-center gap-2 rounded-lg border border-zinc-300 px-6 py-3 text-zinc-700 shadow-xs transition-all duration-300 hover:bg-zinc-100 hover:shadow-md dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
class="group inline-flex items-center gap-2 rounded-lg border border-zinc-300 px-6 py-3 text-zinc-700 shadow-sm transition-all duration-300 hover:bg-zinc-100 hover:shadow-md dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -89,9 +89,9 @@ import Layout from '../layouts/Layout.astro';
|
||||
|
||||
<!-- Random fun fact -->
|
||||
<div
|
||||
class="mx-auto mt-16 max-w-md rounded-xl border border-zinc-100 bg-zinc-50 p-6 shadow-xs backdrop-blur-xs dark:border-zinc-700/50 dark:bg-zinc-800/50"
|
||||
class="mx-auto mt-16 max-w-md rounded-xl border border-zinc-100 bg-zinc-50 p-6 shadow-sm backdrop-blur-sm dark:border-zinc-700/50 dark:bg-zinc-800/50"
|
||||
>
|
||||
<h3 class="text-sm font-medium tracking-wider text-zinc-500 uppercase dark:text-zinc-400">
|
||||
<h3 class="text-sm font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
|
||||
Did you know?
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-zinc-700 dark:text-zinc-300" id="fun-fact">
|
||||
|
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import DynamicIcon from '../components/DynamicIcon.tsx';
|
||||
import { FaJs, FaReact, FaNodeJs, FaPython } from 'react-icons/fa';
|
||||
import { SiTypescript, SiAstro } from 'react-icons/si';
|
||||
|
||||
import directus from '../../lib/directus';
|
||||
import { readSingleton, readItems } from '@directus/sdk';
|
||||
@@ -21,18 +22,18 @@ const skills = await directus.request(
|
||||
<div class="relative mb-12 sm:mb-16 md:mb-20">
|
||||
<!-- Decorative elements -->
|
||||
<div
|
||||
class="animate-blob theme-transition-bg absolute -top-10 -left-10 h-36 w-36 rounded-full bg-zinc-100 opacity-30 blur-3xl sm:-top-20 sm:-left-20 sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/30"
|
||||
class="animate-blob theme-transition-bg absolute -left-10 -top-10 h-36 w-36 rounded-full bg-zinc-100 opacity-30 blur-3xl dark:bg-zinc-800/30 sm:-left-20 sm:-top-20 sm:h-48 sm:w-48 md:h-72 md:w-72"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -right-10 -bottom-10 h-36 w-36 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:-right-20 sm:-bottom-20 sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/30"
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -bottom-10 -right-10 h-36 w-36 rounded-full bg-zinc-200 opacity-30 blur-3xl dark:bg-zinc-800/30 sm:-bottom-20 sm:-right-20 sm:h-48 sm:w-48 md:h-72 md:w-72"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="relative grid grid-cols-1 items-center gap-8 md:grid-cols-2 md:gap-12">
|
||||
<div class="order-2 text-center md:order-1 md:text-left">
|
||||
<h1
|
||||
class="theme-transition-color mb-4 text-3xl font-bold tracking-tight text-zinc-900 sm:mb-6 sm:text-4xl md:text-5xl dark:text-zinc-100"
|
||||
class="theme-transition-color mb-4 text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:mb-6 sm:text-4xl md:text-5xl"
|
||||
>
|
||||
Hello, I'm <span
|
||||
class="theme-transition-all bg-gradient-to-r from-zinc-500 to-zinc-900 bg-clip-text text-transparent dark:from-zinc-300 dark:to-zinc-100"
|
||||
@@ -41,7 +42,7 @@ const skills = await directus.request(
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="theme-transition-color mb-6 text-lg leading-relaxed text-zinc-600 sm:mb-8 sm:text-xl dark:text-zinc-400"
|
||||
class="theme-transition-color mb-6 text-lg leading-relaxed text-zinc-600 dark:text-zinc-400 sm:mb-8 sm:text-xl"
|
||||
>
|
||||
{about.background}
|
||||
</p>
|
||||
@@ -55,7 +56,7 @@ const skills = await directus.request(
|
||||
|
||||
<div class="relative order-1 md:order-2">
|
||||
<div
|
||||
class="theme-transition-all mx-auto aspect-square w-full max-w-[280px] overflow-hidden rounded-3xl border-4 border-white shadow-xl sm:max-w-[320px] sm:border-8 sm:shadow-2xl md:max-w-md dark:border-zinc-800"
|
||||
class="theme-transition-all mx-auto aspect-square w-full max-w-[280px] overflow-hidden rounded-3xl border-4 border-white shadow-xl dark:border-zinc-800 sm:max-w-[320px] sm:border-8 sm:shadow-2xl md:max-w-md"
|
||||
>
|
||||
<img
|
||||
src=`${process.env.DIRECTUS_URL ?? "https://directus.alexlebens.dev"}/assets/${global.portrait}`
|
||||
@@ -67,7 +68,7 @@ const skills = await directus.request(
|
||||
|
||||
<!-- Decorative elements -->
|
||||
<div
|
||||
class="theme-transition-all absolute -right-4 -bottom-4 flex h-16 w-16 items-center justify-center rounded-full border-2 border-white bg-zinc-100 shadow-lg sm:-right-6 sm:-bottom-6 sm:h-20 sm:w-20 sm:border-4 md:h-24 md:w-24 dark:border-zinc-900 dark:bg-zinc-800"
|
||||
class="theme-transition-all absolute -bottom-4 -right-4 flex h-16 w-16 items-center justify-center rounded-full border-2 border-white bg-zinc-100 shadow-lg dark:border-zinc-900 dark:bg-zinc-800 sm:-bottom-6 sm:-right-6 sm:h-20 sm:w-20 sm:border-4 md:h-24 md:w-24"
|
||||
>
|
||||
<span class="text-2xl sm:text-3xl">👋</span>
|
||||
</div>
|
||||
@@ -79,18 +80,18 @@ const skills = await directus.request(
|
||||
<div class="theme-transition-all mb-16 sm:mb-20 md:mb-24">
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<h2
|
||||
class="theme-transition-color mb-6 flex items-center justify-center text-2xl font-bold text-zinc-900 sm:mb-8 sm:text-3xl md:justify-start dark:text-zinc-100"
|
||||
class="theme-transition-color mb-6 flex items-center justify-center text-2xl font-bold text-zinc-900 dark:text-zinc-100 sm:mb-8 sm:text-3xl md:justify-start"
|
||||
>
|
||||
<span
|
||||
class="theme-transition-bg mr-4 hidden h-1 w-8 bg-zinc-300 sm:inline-block sm:w-12 dark:bg-zinc-700"
|
||||
class="theme-transition-bg mr-4 hidden h-1 w-8 bg-zinc-300 dark:bg-zinc-700 sm:inline-block sm:w-12"
|
||||
></span>
|
||||
About Me
|
||||
<span
|
||||
class="theme-transition-bg ml-4 hidden h-1 w-8 bg-zinc-300 sm:inline-block sm:w-12 dark:bg-zinc-700"
|
||||
class="theme-transition-bg ml-4 hidden h-1 w-8 bg-zinc-300 dark:bg-zinc-700 sm:inline-block sm:w-12"
|
||||
></span>
|
||||
</h2>
|
||||
|
||||
<div class="theme-transition-all prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div class="theme-transition-all prose prose-zinc max-w-none dark:prose-invert">
|
||||
<p class="theme-transition-color mb-4 text-base leading-relaxed sm:mb-6 sm:text-lg">
|
||||
{about.experience}
|
||||
</p>
|
||||
@@ -106,10 +107,10 @@ const skills = await directus.request(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skills Section - Improved for mobile -->
|
||||
<!-- Skills Section -->
|
||||
<div class="theme-transition-all mb-16 sm:mb-20 md:mb-24">
|
||||
<h2
|
||||
class="theme-transition-color mb-8 text-center text-2xl font-bold text-zinc-900 sm:mb-12 sm:text-3xl dark:text-zinc-100"
|
||||
class="theme-transition-color mb-8 text-center text-2xl font-bold text-zinc-900 dark:text-zinc-100 sm:mb-12 sm:text-3xl"
|
||||
>
|
||||
Tech Stack
|
||||
</h2>
|
||||
@@ -118,31 +119,37 @@ const skills = await directus.request(
|
||||
<!-- Main slider container -->
|
||||
<div class="slider-track animate-slide flex">
|
||||
{
|
||||
[...skills, ...skills, ...skills].map((skill) => (
|
||||
<div class="skill-card theme-transition-element mx-2 min-w-[220px] transform rounded-xl border border-zinc-200 bg-white transition-all duration-300 hover:-translate-y-2 hover:scale-105 hover:border-zinc-300 hover:shadow-xl sm:mx-4 sm:min-w-[280px] dark:border-zinc-700 dark:bg-zinc-800/50 dark:hover:border-zinc-600">
|
||||
skills.map((skill, index) => (
|
||||
<div
|
||||
key={`${skill.title}-${index}`}
|
||||
class="skill-card theme-transition-element mx-2 min-w-[220px] transform rounded-xl border border-zinc-200 bg-white transition-all duration-300 hover:-translate-y-2 hover:scale-105 hover:border-zinc-300 hover:shadow-xl dark:border-zinc-700 dark:bg-zinc-800/50 dark:hover:border-zinc-600 sm:mx-4 sm:min-w-[280px]"
|
||||
>
|
||||
<div class="p-4 sm:p-6">
|
||||
<div class="mb-4 flex items-center justify-between sm:mb-6">
|
||||
<div class="flex items-center gap-2 sm:gap-4">
|
||||
<div class="theme-transition-bg theme-transition-color flex h-8 w-8 transform items-center justify-center rounded-lg bg-zinc-100 text-zinc-800 transition-transform group-hover:rotate-12 sm:h-12 sm:w-12 dark:bg-zinc-800 dark:text-zinc-200">
|
||||
<DynamicIcon name={skill.icon} />
|
||||
<div class="theme-transition-bg theme-transition-color flex h-8 w-8 transform items-center justify-center rounded-lg bg-zinc-100 text-zinc-800 transition-transform group-hover:rotate-12 dark:bg-zinc-800 dark:text-zinc-200 sm:h-12 sm:w-12">
|
||||
<skill.icon
|
||||
size={20}
|
||||
className="sm:text-2xl transform transition-all hover:scale-125"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="theme-transition-color text-base font-semibold text-zinc-900 sm:text-xl dark:text-zinc-100">
|
||||
<h3 class="theme-transition-color text-base font-semibold text-zinc-900 dark:text-zinc-100 sm:text-xl">
|
||||
{skill.title}
|
||||
</h3>
|
||||
</div>
|
||||
<span class="theme-transition-all rounded-full bg-zinc-100 px-2 py-0.5 font-mono text-xs text-zinc-600 sm:px-2.5 sm:py-1 sm:text-sm dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<span class="theme-transition-all rounded-full bg-zinc-100 px-2 py-0.5 font-mono text-xs text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400 sm:px-2.5 sm:py-1 sm:text-sm">
|
||||
{skill.level}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="theme-transition-bg relative h-1.5 w-full overflow-hidden rounded-full bg-zinc-100 sm:h-2 dark:bg-zinc-700">
|
||||
<div class="theme-transition-bg relative h-1.5 w-full overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-700 sm:h-2">
|
||||
<div
|
||||
class="progress-bar-animate theme-transition-bg absolute top-0 left-0 h-full rounded-full bg-gradient-to-r from-zinc-700 via-zinc-600 to-zinc-800 transition-all duration-1000 dark:from-zinc-300 dark:via-zinc-400 dark:to-zinc-200"
|
||||
class="progress-bar-animate theme-transition-bg absolute left-0 top-0 h-full rounded-full bg-gradient-to-r from-zinc-700 via-zinc-600 to-zinc-800 transition-all duration-1000 dark:from-zinc-300 dark:via-zinc-400 dark:to-zinc-200"
|
||||
style={`width: ${skill.level}%`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="theme-transition-color mt-1 flex justify-between font-mono text-[10px] text-zinc-400 sm:mt-2 sm:text-xs dark:text-zinc-500">
|
||||
<div class="theme-transition-color mt-1 flex justify-between font-mono text-[10px] text-zinc-400 dark:text-zinc-500 sm:mt-2 sm:text-xs">
|
||||
<span>Beginner</span>
|
||||
<span>Advanced</span>
|
||||
</div>
|
||||
@@ -154,11 +161,11 @@ const skills = await directus.request(
|
||||
|
||||
<!-- Gradient overlays for smooth fade effect -->
|
||||
<div
|
||||
class="theme-transition-bg absolute top-0 bottom-0 left-0 z-10 w-12 bg-gradient-to-r from-white to-transparent sm:w-24 dark:from-zinc-900"
|
||||
class="theme-transition-bg absolute bottom-0 left-0 top-0 z-10 w-12 bg-gradient-to-r from-white to-transparent dark:from-zinc-900 sm:w-24"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="theme-transition-bg absolute top-0 right-0 bottom-0 z-10 w-12 bg-gradient-to-l from-white to-transparent sm:w-24 dark:from-zinc-900"
|
||||
class="theme-transition-bg absolute bottom-0 right-0 top-0 z-10 w-12 bg-gradient-to-l from-white to-transparent dark:from-zinc-900 sm:w-24"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -167,12 +174,12 @@ const skills = await directus.request(
|
||||
<!-- Contact Section -->
|
||||
<div class="theme-transition-all mx-auto max-w-3xl text-center">
|
||||
<h2
|
||||
class="theme-transition-color mb-4 text-2xl font-bold text-zinc-900 sm:mb-6 sm:text-3xl dark:text-zinc-100"
|
||||
class="theme-transition-color mb-4 text-2xl font-bold text-zinc-900 dark:text-zinc-100 sm:mb-6 sm:text-3xl"
|
||||
>
|
||||
Get in Touch
|
||||
</h2>
|
||||
<p
|
||||
class="theme-transition-color mb-6 text-base text-zinc-600 sm:mb-8 sm:text-lg dark:text-zinc-400"
|
||||
class="theme-transition-color mb-6 text-base text-zinc-600 dark:text-zinc-400 sm:mb-8 sm:text-lg"
|
||||
>
|
||||
I'm always open to new opportunities and collaborations. If you'd like to work together or
|
||||
just say hello, feel free to reach out.
|
||||
@@ -180,7 +187,7 @@ const skills = await directus.request(
|
||||
|
||||
<a
|
||||
href=`mailto:${global.email}`
|
||||
class="hover theme-transition-all inline-flex items-center justify-center rounded-lg bg-zinc-900 px-6 py-3 text-base font-medium text-zinc-100 transition-colors hover:bg-zinc-700 sm:px-8 sm:py-4 sm:text-lg dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-300"
|
||||
class="theme-transition-all inline-flex items-center justify-center rounded-lg bg-zinc-900 px-6 py-3 text-base font-medium text-zinc-100 transition-colors hover:bg-zinc-700 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-300 sm:px-8 sm:py-4 sm:text-lg"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@@ -43,23 +43,23 @@ const { post, nextPost, prevPost } = Astro.props;
|
||||
>
|
||||
<!-- Main Content - Enhanced with better typography and spacing -->
|
||||
<div
|
||||
class="prose prose-sm prose-zinc dark:prose-invert sm:prose-base prose-headings:scroll-mt-24 prose-headings:font-semibold prose-a:font-medium prose-a:text-zinc-800 prose-a:underline-offset-4 hover:prose-a:text-zinc-600 prose-img:rounded-xl dark:prose-a:text-zinc-300 dark:hover:prose-a:text-zinc-100 max-w-none"
|
||||
class="prose prose-sm prose-zinc max-w-none dark:prose-invert sm:prose-base prose-headings:scroll-mt-24 prose-headings:font-semibold prose-a:font-medium prose-a:text-zinc-800 prose-a:underline-offset-4 hover:prose-a:text-zinc-600 prose-img:rounded-xl dark:prose-a:text-zinc-300 dark:hover:prose-a:text-zinc-100"
|
||||
>
|
||||
<div set:html={post.content} />
|
||||
</div>
|
||||
|
||||
<!-- Next/Previous Navigation - Improved responsive design -->
|
||||
<div
|
||||
class="mt-12 grid grid-cols-1 gap-4 border-t border-zinc-200 pt-8 sm:mt-16 sm:gap-6 sm:pt-12 md:grid-cols-2 dark:border-zinc-800"
|
||||
class="mt-12 grid grid-cols-1 gap-4 border-t border-zinc-200 pt-8 dark:border-zinc-800 sm:mt-16 sm:gap-6 sm:pt-12 md:grid-cols-2"
|
||||
>
|
||||
{
|
||||
prevPost && (
|
||||
<a
|
||||
href={`/blog/${prevPost.slug}`}
|
||||
class="group relative flex h-full flex-col overflow-hidden rounded-xl border border-zinc-200 p-4 transition-all duration-300 hover:-translate-y-1 hover:bg-zinc-50 sm:p-6 dark:border-zinc-800 dark:hover:bg-zinc-800/50"
|
||||
class="group relative flex h-full flex-col overflow-hidden rounded-xl border border-zinc-200 p-4 transition-all duration-300 hover:-translate-y-1 hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-800/50 sm:p-6"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-zinc-100 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:from-zinc-800 dark:to-transparent" />
|
||||
<span class="relative z-10 mb-1 flex items-center gap-1 text-xs font-medium text-zinc-500 sm:mb-2 sm:gap-2 sm:text-sm dark:text-zinc-400">
|
||||
<span class="relative z-10 mb-1 flex items-center gap-1 text-xs font-medium text-zinc-500 dark:text-zinc-400 sm:mb-2 sm:gap-2 sm:text-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
@@ -76,7 +76,7 @@ const { post, nextPost, prevPost } = Astro.props;
|
||||
</svg>
|
||||
Previous Article
|
||||
</span>
|
||||
<h3 class="line-clamp-2 text-base font-medium text-zinc-900 transition-colors group-hover:text-zinc-700 sm:text-lg dark:text-white dark:group-hover:text-zinc-300">
|
||||
<h3 class="line-clamp-2 text-base font-medium text-zinc-900 transition-colors group-hover:text-zinc-700 dark:text-white dark:group-hover:text-zinc-300 sm:text-lg">
|
||||
{prevPost.title}
|
||||
</h3>
|
||||
</a>
|
||||
@@ -86,10 +86,10 @@ const { post, nextPost, prevPost } = Astro.props;
|
||||
nextPost && (
|
||||
<a
|
||||
href={`/blog/${nextPost.slug}`}
|
||||
class="group relative flex h-full flex-col overflow-hidden rounded-xl border border-zinc-200 p-4 transition-all duration-300 hover:-translate-y-1 hover:bg-zinc-50 sm:p-6 md:text-right dark:border-zinc-800 dark:hover:bg-zinc-800/50"
|
||||
class="group relative flex h-full flex-col overflow-hidden rounded-xl border border-zinc-200 p-4 transition-all duration-300 hover:-translate-y-1 hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-800/50 sm:p-6 md:text-right"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gradient-to-l from-zinc-100 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:from-zinc-800 dark:to-transparent" />
|
||||
<span class="relative z-10 mb-1 flex items-center gap-1 text-xs font-medium text-zinc-500 sm:mb-2 sm:gap-2 sm:text-sm md:justify-end dark:text-zinc-400">
|
||||
<span class="relative z-10 mb-1 flex items-center gap-1 text-xs font-medium text-zinc-500 dark:text-zinc-400 sm:mb-2 sm:gap-2 sm:text-sm md:justify-end">
|
||||
Next Article
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -106,7 +106,7 @@ const { post, nextPost, prevPost } = Astro.props;
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
<h3 class="line-clamp-2 text-base font-medium text-zinc-900 transition-colors group-hover:text-zinc-700 sm:text-lg dark:text-white dark:group-hover:text-zinc-300">
|
||||
<h3 class="line-clamp-2 text-base font-medium text-zinc-900 transition-colors group-hover:text-zinc-700 dark:text-white dark:group-hover:text-zinc-300 sm:text-lg">
|
||||
{nextPost.title}
|
||||
</h3>
|
||||
</a>
|
||||
@@ -274,67 +274,67 @@ const { post, nextPost, prevPost } = Astro.props;
|
||||
|
||||
/* Enhanced typography for blog content - Responsive adjustments */
|
||||
.prose {
|
||||
@reference text-zinc-800 dark:text-zinc-200;
|
||||
@apply text-zinc-800 dark:text-zinc-200;
|
||||
}
|
||||
|
||||
.prose h1,
|
||||
.prose h2,
|
||||
.prose h3,
|
||||
.prose h4 {
|
||||
@reference font-semibold text-zinc-900 dark:text-zinc-100;
|
||||
@apply font-semibold text-zinc-900 dark:text-zinc-100;
|
||||
}
|
||||
|
||||
.prose h1 {
|
||||
@reference text-2xl sm:text-3xl md:text-4xl;
|
||||
@apply text-2xl sm:text-3xl md:text-4xl;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
@reference mb-3 mt-8 border-b border-zinc-200 pb-2 text-xl dark:border-zinc-800 sm:mb-4 sm:mt-12 sm:text-2xl;
|
||||
@apply mb-3 mt-8 border-b border-zinc-200 pb-2 text-xl dark:border-zinc-800 sm:mb-4 sm:mt-12 sm:text-2xl;
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
@reference mb-2 mt-6 text-lg sm:mb-3 sm:mt-8 sm:text-xl;
|
||||
@apply mb-2 mt-6 text-lg sm:mb-3 sm:mt-8 sm:text-xl;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
@reference mb-4 text-sm leading-relaxed sm:mb-6 sm:text-base;
|
||||
@apply mb-4 text-sm leading-relaxed sm:mb-6 sm:text-base;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
@reference font-medium text-zinc-800 underline decoration-zinc-400 underline-offset-2 transition-colors hover:text-zinc-600 hover:decoration-zinc-600 dark:text-zinc-300 dark:decoration-zinc-600 dark:hover:text-zinc-100 dark:hover:decoration-zinc-400;
|
||||
@apply font-medium text-zinc-800 underline decoration-zinc-400 underline-offset-2 transition-colors hover:text-zinc-600 hover:decoration-zinc-600 dark:text-zinc-300 dark:decoration-zinc-600 dark:hover:text-zinc-100 dark:hover:decoration-zinc-400;
|
||||
}
|
||||
|
||||
.prose blockquote {
|
||||
@reference my-4 border-l-4 border-zinc-300 pl-4 italic text-zinc-700 dark:border-zinc-700 dark:text-zinc-300 sm:my-6;
|
||||
@apply my-4 border-l-4 border-zinc-300 pl-4 italic text-zinc-700 dark:border-zinc-700 dark:text-zinc-300 sm:my-6;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
@reference rounded-sm bg-zinc-100 px-1.5 py-0.5 text-sm font-medium text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200;
|
||||
@apply rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-medium text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200;
|
||||
}
|
||||
|
||||
.prose pre {
|
||||
@reference my-4 overflow-x-auto rounded-lg bg-[#1e293b] p-3 text-xs text-zinc-200 shadow-md dark:bg-[#1e293b] sm:my-6 sm:p-4 sm:text-sm !important;
|
||||
@apply my-4 overflow-x-auto rounded-lg bg-[#1e293b] p-3 text-xs text-zinc-200 shadow-md dark:bg-[#1e293b] sm:my-6 sm:p-4 sm:text-sm !important;
|
||||
}
|
||||
|
||||
.prose pre code {
|
||||
@reference bg-transparent p-0 text-zinc-200 dark:text-zinc-200 !important;
|
||||
@apply bg-transparent p-0 text-zinc-200 dark:text-zinc-200 !important;
|
||||
}
|
||||
|
||||
.prose img {
|
||||
@reference mx-auto my-6 h-auto max-w-full rounded-lg shadow-md sm:my-8;
|
||||
@apply mx-auto my-6 h-auto max-w-full rounded-lg shadow-md sm:my-8;
|
||||
}
|
||||
|
||||
.prose ul,
|
||||
.prose ol {
|
||||
@reference my-4 pl-5 sm:my-6 sm:pl-6;
|
||||
@apply my-4 pl-5 sm:my-6 sm:pl-6;
|
||||
}
|
||||
|
||||
.prose li {
|
||||
@reference mb-1 text-sm sm:mb-2 sm:text-base;
|
||||
@apply mb-1 text-sm sm:mb-2 sm:text-base;
|
||||
}
|
||||
|
||||
.prose hr {
|
||||
@reference my-8 border-zinc-200 dark:border-zinc-800 sm:my-10;
|
||||
@apply my-8 border-zinc-200 dark:border-zinc-800 sm:my-10;
|
||||
}
|
||||
|
||||
/* Line clamp for truncating text */
|
||||
|
@@ -36,23 +36,23 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
<div class="relative mb-12 sm:mb-20">
|
||||
<!-- Decorative elements -->
|
||||
<div
|
||||
class="animate-blob absolute -top-10 -left-10 h-48 w-48 rounded-full bg-zinc-100 opacity-30 blur-3xl sm:-top-20 sm:-left-20 sm:h-72 sm:w-72 dark:bg-zinc-800/30"
|
||||
class="animate-blob absolute -left-10 -top-10 h-48 w-48 rounded-full bg-zinc-100 opacity-30 blur-3xl dark:bg-zinc-800/30 sm:-left-20 sm:-top-20 sm:h-72 sm:w-72"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 absolute -right-10 -bottom-10 h-48 w-48 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:-right-20 sm:-bottom-20 sm:h-72 sm:w-72 dark:bg-zinc-800/30"
|
||||
class="animate-blob animation-delay-2000 absolute -bottom-10 -right-10 h-48 w-48 rounded-full bg-zinc-200 opacity-30 blur-3xl dark:bg-zinc-800/30 sm:-bottom-20 sm:-right-20 sm:h-72 sm:w-72"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="relative text-center">
|
||||
<h1
|
||||
class="mb-4 text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl md:text-5xl dark:text-zinc-100"
|
||||
class="mb-4 text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:text-4xl md:text-5xl"
|
||||
>
|
||||
Blog
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="mx-auto mb-6 max-w-2xl text-sm text-zinc-600 sm:mb-10 sm:text-base dark:text-zinc-400"
|
||||
class="mx-auto mb-6 max-w-2xl text-sm text-zinc-600 dark:text-zinc-400 sm:mb-10 sm:text-base"
|
||||
>
|
||||
Thoughts, ideas, and explorations on technology and selfhosting.
|
||||
</p>
|
||||
@@ -65,23 +65,23 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
{
|
||||
sortedPosts.length > 0 && (
|
||||
<div class="mb-8 sm:mb-12 md:col-span-12">
|
||||
<article class="group relative overflow-hidden rounded-none border-b border-zinc-200 pb-6 sm:pb-8 dark:border-zinc-800">
|
||||
<article class="group relative overflow-hidden rounded-none border-b border-zinc-200 pb-6 dark:border-zinc-800 sm:pb-8">
|
||||
<div class="flex h-full flex-col gap-6 sm:gap-8 md:flex-row">
|
||||
{sortedPosts[0].image && (
|
||||
<div class="mx-auto h-60 w-full max-w-full overflow-hidden sm:h-80 sm:max-w-md md:mx-0 md:h-96 md:w-1/2">
|
||||
<img
|
||||
src={`${process.env.DIRECTUS_URL ?? 'https://directus.alexlebens.dev'}/assets/${sortedPosts[0].image}`}
|
||||
alt={sortedPosts[0].title}
|
||||
class="h-full w-full object-cover grayscale transition-all duration-700 group-hover:scale-105 hover:grayscale-0"
|
||||
class="h-full w-full object-cover grayscale transition-all duration-700 hover:grayscale-0 group-hover:scale-105"
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex flex-1 flex-col justify-center">
|
||||
<div class="mb-3 flex items-center justify-center gap-2 text-xs text-zinc-500 sm:text-sm md:justify-start dark:text-zinc-400">
|
||||
<span class="font-medium tracking-wider uppercase">Featured</span>
|
||||
<span class="h-px w-6 bg-zinc-300 sm:w-8 dark:bg-zinc-700" />
|
||||
<div class="mb-3 flex items-center justify-center gap-2 text-xs text-zinc-500 dark:text-zinc-400 sm:text-sm md:justify-start">
|
||||
<span class="font-medium uppercase tracking-wider">Featured</span>
|
||||
<span class="h-px w-6 bg-zinc-300 dark:bg-zinc-700 sm:w-8" />
|
||||
{sortedPosts[0].published_date && (
|
||||
<time datetime={sortedPosts[0].published_date.toLocaleString()}>
|
||||
{sortedPosts[0].published_date.toLocaleString('en-US', {
|
||||
@@ -93,7 +93,7 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h2 class="mb-3 text-center text-2xl font-bold text-zinc-900 transition-colors group-hover:text-zinc-700 sm:mb-4 sm:text-3xl md:text-left dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<h2 class="mb-3 text-center text-2xl font-bold text-zinc-900 transition-colors group-hover:text-zinc-700 dark:text-zinc-100 dark:group-hover:text-zinc-300 sm:mb-4 sm:text-3xl md:text-left">
|
||||
<a
|
||||
href={`/blog/${sortedPosts[0].slug}/`}
|
||||
class="before:absolute before:inset-0"
|
||||
@@ -102,7 +102,7 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<p class="mb-4 line-clamp-3 text-center text-sm text-zinc-600 sm:mb-6 sm:text-base md:text-left dark:text-zinc-400">
|
||||
<p class="mb-4 line-clamp-3 text-center text-sm text-zinc-600 dark:text-zinc-400 sm:mb-6 sm:text-base md:text-left">
|
||||
{sortedPosts[0].description}
|
||||
</p>
|
||||
|
||||
@@ -110,7 +110,7 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
{sortedPosts[0].tags && (
|
||||
<div class="flex flex-wrap justify-center gap-2 md:justify-start">
|
||||
{sortedPosts[0].tags.slice(0, 2).map((tag) => (
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs tracking-wider text-zinc-600 uppercase sm:px-3 dark:border-zinc-800 dark:text-zinc-400">
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs uppercase tracking-wider text-zinc-600 dark:border-zinc-800 dark:text-zinc-400 sm:px-3">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
@@ -128,7 +128,7 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
<div class="relative md:col-span-3">
|
||||
<div class="mb-8 space-y-4 md:sticky md:top-24 md:mb-0">
|
||||
<h3
|
||||
class="mb-4 text-center text-lg font-medium tracking-wider text-zinc-900 uppercase md:text-left dark:text-zinc-100"
|
||||
class="mb-4 text-center text-lg font-medium uppercase tracking-wider text-zinc-900 dark:text-zinc-100 md:text-left"
|
||||
>
|
||||
Archive
|
||||
</h3>
|
||||
@@ -141,12 +141,12 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
years.map((year, index) => (
|
||||
<a
|
||||
href={`#year-${year}`}
|
||||
class={`hover mr-3 flex items-center rounded-full border-b border-zinc-100 px-4 py-2 whitespace-nowrap transition-colors hover:bg-zinc-50 md:mr-0 md:w-full md:rounded-none md:px-0 md:py-3 md:whitespace-normal dark:border-zinc-800 dark:hover:bg-zinc-900 ${index === 0 ? 'bg-zinc-50 dark:bg-zinc-800/50' : ''}`}
|
||||
class={`mr-3 flex items-center whitespace-nowrap rounded-full border-b border-zinc-100 px-4 py-2 transition-colors hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-900 md:mr-0 md:w-full md:whitespace-normal md:rounded-none md:px-0 md:py-3 ${index === 0 ? 'bg-zinc-50 dark:bg-zinc-800/50' : ''}`}
|
||||
>
|
||||
<span class="text-base font-medium text-zinc-900 md:text-lg dark:text-zinc-100">
|
||||
<span class="text-base font-medium text-zinc-900 dark:text-zinc-100 md:text-lg">
|
||||
{year}
|
||||
</span>
|
||||
<span class="ml-2 text-xs text-zinc-500 md:ml-auto md:text-sm dark:text-zinc-400">
|
||||
<span class="ml-2 text-xs text-zinc-500 dark:text-zinc-400 md:ml-auto md:text-sm">
|
||||
{postsByYear[year].length} post{postsByYear[year].length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
</a>
|
||||
@@ -161,7 +161,7 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
{
|
||||
years.map((year) => (
|
||||
<div id={`year-${year}`} class="mb-12 scroll-mt-16 sm:mb-20">
|
||||
<h2 class="mb-6 border-b border-zinc-200 pb-3 text-center text-xl font-bold text-zinc-900 sm:mb-8 sm:pb-4 sm:text-2xl md:text-left dark:border-zinc-800 dark:text-zinc-100">
|
||||
<h2 class="mb-6 border-b border-zinc-200 pb-3 text-center text-xl font-bold text-zinc-900 dark:border-zinc-800 dark:text-zinc-100 sm:mb-8 sm:pb-4 sm:text-2xl md:text-left">
|
||||
{year}
|
||||
</h2>
|
||||
|
||||
@@ -175,14 +175,14 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
<img
|
||||
src={`${process.env.DIRECTUS_URL ?? 'https://directus.alexlebens.dev'}/assets/${post.image}`}
|
||||
alt={post.title}
|
||||
class="h-full w-full object-cover grayscale transition-all duration-700 group-hover:scale-105 hover:grayscale-0"
|
||||
class="h-full w-full object-cover grayscale transition-all duration-700 hover:grayscale-0 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:gap-4 sm:text-sm md:justify-start dark:text-zinc-400">
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 dark:text-zinc-400 sm:mb-3 sm:gap-4 sm:text-sm md:justify-start">
|
||||
{post.pubDate && (
|
||||
<time
|
||||
datetime={post.published_date.toLocaleString()}
|
||||
@@ -196,25 +196,25 @@ const allTags = [...new Set(sortedPosts.flatMap((post) => post.tags || []))];
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 class="mb-2 text-center text-lg font-semibold text-zinc-900 transition-colors group-hover:text-zinc-700 sm:mb-3 sm:text-xl md:text-left dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<h3 class="mb-2 text-center text-lg font-semibold text-zinc-900 transition-colors group-hover:text-zinc-700 dark:text-zinc-100 dark:group-hover:text-zinc-300 sm:mb-3 sm:text-xl md:text-left">
|
||||
<a href={`/blog/${post.slug}/`} class="before:absolute before:inset-0">
|
||||
{post.title}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<p class="mb-4 line-clamp-2 grow text-center text-sm text-zinc-600 md:text-left dark:text-zinc-400">
|
||||
<p class="mb-4 line-clamp-2 flex-grow text-center text-sm text-zinc-600 dark:text-zinc-400 md:text-left">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
{post.tags && (
|
||||
<div class="mt-auto flex flex-wrap justify-center gap-2 md:justify-start">
|
||||
{post.tags.slice(0, 2).map((tag) => (
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs tracking-wider text-zinc-600 uppercase sm:px-3 dark:border-zinc-800 dark:text-zinc-400">
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs uppercase tracking-wider text-zinc-600 dark:border-zinc-800 dark:text-zinc-400 sm:px-3">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{post.tags.length > 2 && (
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs tracking-wider text-zinc-600 uppercase sm:px-3 dark:border-zinc-800 dark:text-zinc-400">
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs uppercase tracking-wider text-zinc-600 dark:border-zinc-800 dark:text-zinc-400 sm:px-3">
|
||||
+{post.tags.length - 2}
|
||||
</span>
|
||||
)}
|
||||
|
@@ -27,17 +27,17 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
<div class="relative mx-auto max-w-2xl">
|
||||
<!-- Adjusted blob positions and sizes for better mobile appearance -->
|
||||
<div
|
||||
class="animate-blob theme-transition-bg absolute -top-10 -left-10 h-40 w-40 rounded-full bg-zinc-100 opacity-50 blur-3xl sm:-top-20 sm:-left-20 sm:h-64 sm:w-64 dark:bg-zinc-800/50"
|
||||
class="animate-blob theme-transition-bg absolute -left-10 -top-10 h-40 w-40 rounded-full bg-zinc-100 opacity-50 blur-3xl dark:bg-zinc-800/50 sm:-left-20 sm:-top-20 sm:h-64 sm:w-64"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -right-10 -bottom-10 h-40 w-40 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:-right-20 sm:-bottom-20 sm:h-64 sm:w-64 dark:bg-zinc-800/30"
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -bottom-10 -right-10 h-40 w-40 rounded-full bg-zinc-200 opacity-30 blur-3xl dark:bg-zinc-800/30 sm:-bottom-20 sm:-right-20 sm:h-64 sm:w-64"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="relative text-center sm:text-left">
|
||||
<h1
|
||||
class="theme-transition-color hero-text text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl md:text-5xl lg:text-6xl dark:text-zinc-100"
|
||||
class="theme-transition-color hero-text text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:text-4xl md:text-5xl lg:text-6xl"
|
||||
>
|
||||
<span class="block">Writing on technology,</span>
|
||||
<span class="mt-1 block">development, and</span>
|
||||
@@ -51,7 +51,7 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
</span>
|
||||
</h1>
|
||||
<p
|
||||
class="theme-transition-color mx-auto mt-4 max-w-lg text-base leading-relaxed text-zinc-600 sm:mx-0 sm:mt-6 sm:text-lg md:mt-8 dark:text-zinc-400"
|
||||
class="theme-transition-color mx-auto mt-4 max-w-lg text-base leading-relaxed text-zinc-600 dark:text-zinc-400 sm:mx-0 sm:mt-6 sm:text-lg md:mt-8"
|
||||
>
|
||||
{global.about}
|
||||
</p>
|
||||
@@ -87,20 +87,20 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
|
||||
<!-- Featured Post Section - Improved for mobile -->
|
||||
<section
|
||||
class="theme-transition-all border-t border-zinc-100 px-4 py-10 sm:px-6 sm:py-12 md:py-16 dark:border-zinc-800"
|
||||
class="theme-transition-all border-t border-zinc-100 px-4 py-10 dark:border-zinc-800 sm:px-6 sm:py-12 md:py-16"
|
||||
>
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<div
|
||||
class="mb-6 flex flex-col justify-between gap-4 sm:mb-8 sm:flex-row sm:items-center md:mb-12"
|
||||
>
|
||||
<h2
|
||||
class="theme-transition-color text-center text-xl font-bold tracking-tight text-zinc-900 sm:text-left sm:text-2xl md:text-3xl dark:text-zinc-100"
|
||||
class="theme-transition-color text-center text-xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:text-left sm:text-2xl md:text-3xl"
|
||||
>
|
||||
Recent Posts
|
||||
</h2>
|
||||
<a
|
||||
href="/blog"
|
||||
class="theme-transition-color group relative flex min-h-[44px] items-center justify-center self-center text-sm font-medium text-zinc-900 hover:text-zinc-700 sm:self-auto dark:text-zinc-100 dark:hover:text-zinc-300"
|
||||
class="theme-transition-color group relative flex min-h-[44px] items-center justify-center self-center text-sm font-medium text-zinc-900 hover:text-zinc-700 dark:text-zinc-100 dark:hover:text-zinc-300 sm:self-auto"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
View all posts
|
||||
@@ -129,7 +129,7 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
{
|
||||
recentPosts.map((post, index) => (
|
||||
<article class="hover-3d theme-transition-element group relative mx-auto flex w-full max-w-sm flex-col items-start sm:mx-0">
|
||||
<div class="theme-transition-bg absolute -inset-x-4 -inset-y-6 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 sm:-inset-x-6 sm:rounded-2xl dark:bg-zinc-800/50" />
|
||||
<div class="theme-transition-bg absolute -inset-x-4 -inset-y-6 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 dark:bg-zinc-800/50 sm:-inset-x-6 sm:rounded-2xl" />
|
||||
|
||||
{post.image && (
|
||||
<div class="relative z-10 mb-4 aspect-video w-full overflow-hidden rounded-lg">
|
||||
@@ -144,13 +144,13 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="theme-transition-color relative z-10 flex w-full flex-wrap items-center justify-center gap-x-3 gap-y-2 text-xs text-zinc-500 sm:justify-start sm:gap-x-4 dark:text-zinc-400">
|
||||
<div class="theme-transition-color relative z-10 flex w-full flex-wrap items-center justify-center gap-x-3 gap-y-2 text-xs text-zinc-500 dark:text-zinc-400 sm:justify-start sm:gap-x-4">
|
||||
<time datetime={post.published_date.toLocaleString()} class="font-medium">
|
||||
<FormattedDate date={post.published_date} />
|
||||
</time>
|
||||
</div>
|
||||
|
||||
<h3 class="theme-transition-color relative z-10 mt-3 w-full text-center text-lg font-semibold tracking-tight text-zinc-900 transition-colors group-hover:text-zinc-700 sm:text-left sm:text-xl dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<h3 class="theme-transition-color relative z-10 mt-3 w-full text-center text-lg font-semibold tracking-tight text-zinc-900 transition-colors group-hover:text-zinc-700 dark:text-zinc-100 dark:group-hover:text-zinc-300 sm:text-left sm:text-xl">
|
||||
<a
|
||||
href={`/blog/${post.slug}`}
|
||||
class="flex min-h-[44px] items-center justify-center sm:justify-start"
|
||||
@@ -160,7 +160,7 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<p class="theme-transition-color relative z-10 mt-2 line-clamp-3 w-full text-center text-sm text-zinc-600 sm:mt-3 sm:text-left dark:text-zinc-400">
|
||||
<p class="theme-transition-color relative z-10 mt-2 line-clamp-3 w-full text-center text-sm text-zinc-600 dark:text-zinc-400 sm:mt-3 sm:text-left">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
@@ -169,7 +169,7 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
{post.tags.slice(0, 3).map((tag) => (
|
||||
<a
|
||||
href={`/topics/${tag}`}
|
||||
class="theme-transition-all inline-flex min-h-[28px] items-center rounded-full bg-zinc-100 px-2 py-1 text-xs font-medium text-zinc-800 transition-colors hover:bg-zinc-200 sm:px-3 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700"
|
||||
class="theme-transition-all inline-flex min-h-[28px] items-center rounded-full bg-zinc-100 px-2 py-1 text-xs font-medium text-zinc-800 transition-colors hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700 sm:px-3"
|
||||
>
|
||||
#{tag}
|
||||
</a>
|
||||
@@ -184,13 +184,13 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
|
||||
<a
|
||||
href={`/blog/${post.slug}`}
|
||||
class="theme-transition-color relative z-10 mx-auto mt-3 flex min-h-[44px] items-center text-sm font-medium text-zinc-700 transition-colors group-hover:text-zinc-900 sm:mx-0 sm:mt-4 dark:text-zinc-300 dark:group-hover:text-zinc-100"
|
||||
class="theme-transition-color relative z-10 mx-auto mt-3 flex min-h-[44px] items-center text-sm font-medium text-zinc-700 transition-colors group-hover:text-zinc-900 dark:text-zinc-300 dark:group-hover:text-zinc-100 sm:mx-0 sm:mt-4"
|
||||
>
|
||||
<span class="relative inline-block overflow-hidden">
|
||||
<span class="block transition-transform duration-300 group-hover:-translate-y-full">
|
||||
Read article
|
||||
</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full whitespace-nowrap transition-transform duration-300 group-hover:translate-y-0">
|
||||
<span class="absolute left-0 top-0 translate-y-full whitespace-nowrap transition-transform duration-300 group-hover:translate-y-0">
|
||||
Explore now
|
||||
</span>
|
||||
</span>
|
||||
@@ -218,9 +218,9 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
<!-- Topics/Tags Section - Improved for mobile -->
|
||||
{
|
||||
allTags.length > 0 && (
|
||||
<section class="theme-transition-all border-t border-zinc-100 px-4 py-10 sm:px-6 sm:py-12 md:py-16 dark:border-zinc-800">
|
||||
<section class="theme-transition-all border-t border-zinc-100 px-4 py-10 dark:border-zinc-800 sm:px-6 sm:py-12 md:py-16">
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<h2 class="theme-transition-color mb-6 text-center text-xl font-bold tracking-tight text-zinc-900 sm:mb-8 sm:text-left sm:text-2xl md:text-3xl dark:text-zinc-100">
|
||||
<h2 class="theme-transition-color mb-6 text-center text-xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:mb-8 sm:text-left sm:text-2xl md:text-3xl">
|
||||
Explore Topics
|
||||
</h2>
|
||||
|
||||
@@ -230,13 +230,13 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
return (
|
||||
<a
|
||||
href={`/topics/${tag}`}
|
||||
class="theme-transition-all group flex min-h-[80px] flex-col rounded-xl border border-zinc-200 p-3 transition-all duration-300 hover:bg-zinc-50 sm:min-h-[90px] sm:p-4 md:p-6 dark:border-zinc-800 dark:hover:bg-zinc-800/70"
|
||||
class="theme-transition-all group flex min-h-[80px] flex-col rounded-xl border border-zinc-200 p-3 transition-all duration-300 hover:bg-zinc-50 dark:border-zinc-800 dark:hover:bg-zinc-800/70 sm:min-h-[90px] sm:p-4 md:p-6"
|
||||
>
|
||||
<div class="mb-2 flex items-start justify-between">
|
||||
<span class="theme-transition-color mr-2 text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
||||
#{tag}
|
||||
</span>
|
||||
<span class="theme-transition-all shrink-0 rounded-full bg-zinc-100 px-2 py-0.5 text-xs text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<span class="theme-transition-all flex-shrink-0 rounded-full bg-zinc-100 px-2 py-0.5 text-xs text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
{tagCount} {tagCount === 1 ? 'post' : 'posts'}
|
||||
</span>
|
||||
</div>
|
||||
|
@@ -52,11 +52,11 @@ const relatedTags = [
|
||||
<!-- Header section -->
|
||||
<div class="relative mb-10 sm:mb-16">
|
||||
<div
|
||||
class="animate-blob absolute -top-20 -left-20 h-48 w-48 rounded-full bg-zinc-100 opacity-30 blur-3xl sm:h-64 sm:w-64 dark:bg-zinc-900/30"
|
||||
class="animate-blob absolute -left-20 -top-20 h-48 w-48 rounded-full bg-zinc-100 opacity-30 blur-3xl dark:bg-zinc-900/30 sm:h-64 sm:w-64"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 absolute -right-10 -bottom-10 h-36 w-36 rounded-full bg-zinc-200 opacity-20 blur-2xl sm:h-48 sm:w-48 dark:bg-zinc-900/20"
|
||||
class="animate-blob animation-delay-2000 absolute -bottom-10 -right-10 h-36 w-36 rounded-full bg-zinc-200 opacity-20 blur-2xl dark:bg-zinc-900/20 sm:h-48 sm:w-48"
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -88,7 +88,7 @@ const relatedTags = [
|
||||
class="mb-2 flex flex-col justify-center gap-4 sm:flex-row sm:items-center sm:justify-start"
|
||||
>
|
||||
<div
|
||||
class="tag-icon mx-auto flex h-12 w-12 items-center justify-center rounded-xl bg-zinc-100 shadow-xs sm:mx-0 dark:bg-zinc-800"
|
||||
class="tag-icon mx-auto flex h-12 w-12 items-center justify-center rounded-xl bg-zinc-100 shadow-sm dark:bg-zinc-800 sm:mx-0"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -108,7 +108,7 @@ const relatedTags = [
|
||||
</div>
|
||||
|
||||
<h1
|
||||
class="text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl dark:text-zinc-100"
|
||||
class="text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:text-4xl"
|
||||
>
|
||||
<span class="relative">
|
||||
#{tag}
|
||||
@@ -122,7 +122,7 @@ const relatedTags = [
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="mx-auto mt-4 max-w-2xl text-base text-zinc-600 sm:mx-0 sm:text-lg dark:text-zinc-400"
|
||||
class="mx-auto mt-4 max-w-2xl text-base text-zinc-600 dark:text-zinc-400 sm:mx-0 sm:text-lg"
|
||||
>
|
||||
Exploring <span class="font-medium text-zinc-900 dark:text-zinc-100"
|
||||
>{sortedPosts.length}</span
|
||||
@@ -137,14 +137,14 @@ const relatedTags = [
|
||||
{
|
||||
relatedTags.length > 0 && (
|
||||
<div class="hide-scrollbar mb-8 overflow-x-auto pb-4 sm:mb-12">
|
||||
<h2 class="mb-3 text-center text-lg font-medium text-zinc-900 sm:text-left dark:text-zinc-100">
|
||||
<h2 class="mb-3 text-center text-lg font-medium text-zinc-900 dark:text-zinc-100 sm:text-left">
|
||||
Related topics
|
||||
</h2>
|
||||
<div class="flex flex-nowrap justify-center gap-2 sm:justify-start">
|
||||
{relatedTags.map((relatedTag) => (
|
||||
<a
|
||||
href={`/topics/${relatedTag}`}
|
||||
class="inline-flex shrink-0 items-center rounded-full bg-zinc-100 px-3 py-1.5 text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700"
|
||||
class="inline-flex flex-shrink-0 items-center rounded-full bg-zinc-100 px-3 py-1.5 text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700"
|
||||
>
|
||||
#{relatedTag}
|
||||
</a>
|
||||
@@ -162,12 +162,12 @@ const relatedTags = [
|
||||
<div class="relative space-y-6 sm:space-y-8">
|
||||
{
|
||||
sortedPosts.map((post) => (
|
||||
<article class="hover-card group relative mx-auto flex max-w-2xl flex-col rounded-2xl border border-zinc-200 p-5 transition-all duration-300 hover:bg-zinc-50/80 hover:shadow-md sm:mx-0 sm:p-8 dark:border-zinc-800 dark:hover:bg-zinc-900/50">
|
||||
<article class="hover-card group relative mx-auto flex max-w-2xl flex-col rounded-2xl border border-zinc-200 p-5 transition-all duration-300 hover:bg-zinc-50/80 hover:shadow-md dark:border-zinc-800 dark:hover:bg-zinc-900/50 sm:mx-0 sm:p-8">
|
||||
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-zinc-50/0 to-zinc-100/0 opacity-0 transition-opacity duration-500 group-hover:opacity-100 dark:from-zinc-900/0 dark:to-zinc-800/0" />
|
||||
|
||||
<div class="flex flex-col gap-5 sm:flex-row sm:gap-6">
|
||||
{post.image && (
|
||||
<div class="mx-auto h-40 w-full shrink-0 overflow-hidden rounded-xl shadow-xs transition-all duration-300 group-hover:shadow-md sm:mx-0 sm:w-56">
|
||||
<div class="mx-auto h-40 w-full flex-shrink-0 overflow-hidden rounded-xl shadow-sm transition-all duration-300 group-hover:shadow-md sm:mx-0 sm:w-56">
|
||||
<img
|
||||
src={`${process.env.DIRECTUS_URL ?? 'https://directus.alexlebens.dev'}/assets/${post.image}?width=500`}
|
||||
alt={post.image_alt}
|
||||
@@ -178,7 +178,7 @@ const relatedTags = [
|
||||
)}
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm dark:text-zinc-400">
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 dark:text-zinc-400 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm">
|
||||
{post.published_date && (
|
||||
<time
|
||||
datetime={post.published_date.toLocaleString()}
|
||||
@@ -204,19 +204,19 @@ const relatedTags = [
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h2 class="mb-2 text-center text-xl font-semibold text-zinc-900 transition-colors group-hover:text-zinc-700 sm:mb-3 sm:text-left sm:text-2xl dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<h2 class="mb-2 text-center text-xl font-semibold text-zinc-900 transition-colors group-hover:text-zinc-700 dark:text-zinc-100 dark:group-hover:text-zinc-300 sm:mb-3 sm:text-left sm:text-2xl">
|
||||
<a href={`/blog/${post.slug}/`} class="before:absolute before:inset-0">
|
||||
{post.title}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<p class="mb-4 line-clamp-2 text-center text-sm text-zinc-600 sm:line-clamp-3 sm:text-left sm:text-base dark:text-zinc-400">
|
||||
<p class="mb-4 line-clamp-2 text-center text-sm text-zinc-600 dark:text-zinc-400 sm:line-clamp-3 sm:text-left sm:text-base">
|
||||
{post.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap items-end justify-center border-t border-zinc-100 pt-4 sm:justify-between dark:border-zinc-800">
|
||||
<div class="mt-4 flex flex-wrap items-end justify-center border-t border-zinc-100 pt-4 dark:border-zinc-800 sm:justify-between">
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<div class="mb-3 flex flex-wrap justify-center gap-2 sm:mb-0 sm:justify-start">
|
||||
{post.tags.slice(0, 3).map((postTag) => (
|
||||
@@ -239,7 +239,7 @@ const relatedTags = [
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="mx-auto sm:mr-0 sm:ml-auto">
|
||||
<div class="mx-auto sm:ml-auto sm:mr-0">
|
||||
<a
|
||||
href={`/blog/${post.slug}/`}
|
||||
class="inline-flex items-center text-sm font-medium text-zinc-700 transition-colors group-hover:text-zinc-900 dark:text-zinc-300 dark:group-hover:text-zinc-100"
|
||||
@@ -250,7 +250,7 @@ const relatedTags = [
|
||||
<span class="block transition-transform duration-300 group-hover:-translate-y-full">
|
||||
Read article
|
||||
</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full whitespace-nowrap transition-transform duration-300 group-hover:translate-y-0">
|
||||
<span class="absolute left-0 top-0 translate-y-full whitespace-nowrap transition-transform duration-300 group-hover:translate-y-0">
|
||||
Explore now
|
||||
</span>
|
||||
</span>
|
||||
@@ -281,14 +281,14 @@ const relatedTags = [
|
||||
{
|
||||
sortedPosts.length === 0 && (
|
||||
<div class="py-12 text-center sm:py-20">
|
||||
<div class="mb-4 inline-flex h-16 w-16 items-center justify-center rounded-full bg-zinc-100 sm:mb-6 sm:h-20 sm:w-20 dark:bg-zinc-800">
|
||||
<div class="mb-4 inline-flex h-16 w-16 items-center justify-center rounded-full bg-zinc-100 dark:bg-zinc-800 sm:mb-6 sm:h-20 sm:w-20">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="h-8 w-8 text-zinc-500 sm:h-10 sm:w-10 dark:text-zinc-400"
|
||||
class="h-8 w-8 text-zinc-500 dark:text-zinc-400 sm:h-10 sm:w-10"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -297,7 +297,7 @@ const relatedTags = [
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="mb-2 text-xl font-semibold text-zinc-900 sm:text-2xl dark:text-zinc-100">
|
||||
<h2 class="mb-2 text-xl font-semibold text-zinc-900 dark:text-zinc-100 sm:text-2xl">
|
||||
No posts found
|
||||
</h2>
|
||||
<p class="text-zinc-600 dark:text-zinc-400">There are no posts with this tag yet.</p>
|
||||
|
@@ -34,25 +34,25 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count);
|
||||
<!-- Enhanced header section with animated elements - improved for mobile -->
|
||||
<div class="theme-transition-element relative mb-8 text-center sm:mb-12 md:mb-16">
|
||||
<div
|
||||
class="animate-blob theme-transition-bg absolute -top-16 -left-16 h-36 w-36 rounded-full bg-zinc-100 opacity-50 blur-3xl sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/50"
|
||||
class="animate-blob theme-transition-bg absolute -left-16 -top-16 h-36 w-36 rounded-full bg-zinc-100 opacity-50 blur-3xl dark:bg-zinc-800/50 sm:h-48 sm:w-48 md:h-72 md:w-72"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -right-16 -bottom-16 h-36 w-36 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/30"
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -bottom-16 -right-16 h-36 w-36 rounded-full bg-zinc-200 opacity-30 blur-3xl dark:bg-zinc-800/30 sm:h-48 sm:w-48 md:h-72 md:w-72"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-4000 theme-transition-bg absolute top-8 right-8 h-24 w-24 rounded-full bg-zinc-100/30 opacity-40 blur-2xl sm:h-32 sm:w-32 md:h-40 md:w-40 dark:bg-zinc-700/20"
|
||||
class="animate-blob animation-delay-4000 theme-transition-bg absolute right-8 top-8 h-24 w-24 rounded-full bg-zinc-100/30 opacity-40 blur-2xl dark:bg-zinc-700/20 sm:h-32 sm:w-32 md:h-40 md:w-40"
|
||||
>
|
||||
</div>
|
||||
|
||||
<h1
|
||||
class="theme-transition-color relative mb-3 text-3xl font-bold tracking-tight text-zinc-900 sm:mb-4 sm:text-4xl md:mb-6 md:text-5xl lg:text-6xl dark:text-zinc-100"
|
||||
class="theme-transition-color relative mb-3 text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:mb-4 sm:text-4xl md:mb-6 md:text-5xl lg:text-6xl"
|
||||
>
|
||||
<span class="relative inline-block">
|
||||
<span class="relative inline-block">
|
||||
<span
|
||||
class="theme-transition-bg absolute -inset-1 rounded-lg bg-gradient-to-r from-zinc-200/50 to-zinc-300/50 blur-xs dark:from-zinc-800/50 dark:to-zinc-700/50"
|
||||
class="theme-transition-bg absolute -inset-1 rounded-lg bg-gradient-to-r from-zinc-200/50 to-zinc-300/50 blur-sm dark:from-zinc-800/50 dark:to-zinc-700/50"
|
||||
></span>
|
||||
<span class="relative">Explore</span>
|
||||
</span>
|
||||
@@ -60,13 +60,13 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count);
|
||||
<span class="relative inline-block">
|
||||
Topics
|
||||
<span
|
||||
class="animate-underline theme-transition-bg absolute -bottom-1 left-0 h-0.5 w-full origin-left transform bg-gradient-to-r from-zinc-400 to-zinc-600 sm:-bottom-2 sm:h-1 dark:from-zinc-600 dark:to-zinc-400"
|
||||
class="animate-underline theme-transition-bg absolute -bottom-1 left-0 h-0.5 w-full origin-left transform bg-gradient-to-r from-zinc-400 to-zinc-600 dark:from-zinc-600 dark:to-zinc-400 sm:-bottom-2 sm:h-1"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
</h1>
|
||||
<p
|
||||
class="theme-transition-color relative mx-auto max-w-2xl text-sm text-zinc-600 sm:text-base md:text-lg lg:text-xl dark:text-zinc-400"
|
||||
class="theme-transition-color relative mx-auto max-w-2xl text-sm text-zinc-600 dark:text-zinc-400 sm:text-base md:text-lg lg:text-xl"
|
||||
>
|
||||
Discover content organized by your interests
|
||||
</p>
|
||||
@@ -75,14 +75,14 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count);
|
||||
{
|
||||
tags.length === 0 ? (
|
||||
<div class="theme-transition-element py-8 text-center sm:py-12 md:py-16">
|
||||
<div class="theme-transition-bg mb-3 inline-flex h-16 w-16 items-center justify-center rounded-full bg-zinc-100 shadow-inner sm:mb-4 sm:h-20 sm:w-20 md:mb-6 md:h-24 md:w-24 dark:bg-zinc-800">
|
||||
<div class="theme-transition-bg mb-3 inline-flex h-16 w-16 items-center justify-center rounded-full bg-zinc-100 shadow-inner dark:bg-zinc-800 sm:mb-4 sm:h-20 sm:w-20 md:mb-6 md:h-24 md:w-24">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="theme-transition-color h-8 w-8 text-zinc-500 sm:h-10 sm:w-10 md:h-12 md:w-12 dark:text-zinc-400"
|
||||
class="theme-transition-color h-8 w-8 text-zinc-500 dark:text-zinc-400 sm:h-10 sm:w-10 md:h-12 md:w-12"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -92,21 +92,21 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count);
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="theme-transition-color text-lg font-medium text-zinc-800 sm:text-xl md:text-2xl dark:text-zinc-200">
|
||||
<p class="theme-transition-color text-lg font-medium text-zinc-800 dark:text-zinc-200 sm:text-xl md:text-2xl">
|
||||
No tags found yet.
|
||||
</p>
|
||||
<p class="theme-transition-color mt-2 text-xs text-zinc-500 sm:text-sm md:text-base dark:text-zinc-500">
|
||||
<p class="theme-transition-color mt-2 text-xs text-zinc-500 dark:text-zinc-500 sm:text-sm md:text-base">
|
||||
Check back later for categorized content.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex w-full justify-center">
|
||||
<div class="tag-cloud hover-3d glass theme-transition-all relative w-full rounded-lg border border-zinc-100 bg-white/50 p-3 backdrop-blur-xs sm:rounded-xl sm:p-4 md:rounded-2xl md:p-6 lg:rounded-3xl lg:p-8 dark:border-zinc-800 dark:bg-zinc-900/50">
|
||||
<div class="tag-cloud hover-3d glass theme-transition-all relative w-full rounded-lg border border-zinc-100 bg-white/50 p-3 backdrop-blur-sm dark:border-zinc-800 dark:bg-zinc-900/50 sm:rounded-xl sm:p-4 md:rounded-2xl md:p-6 lg:rounded-3xl lg:p-8">
|
||||
<div class="bg-grid-pattern theme-transition-bg absolute inset-0 opacity-5 dark:opacity-10" />
|
||||
<div class="theme-transition-bg absolute -top-8 -right-8 h-20 w-20 rounded-full bg-gradient-to-br from-zinc-200/30 to-zinc-300/20 blur-xl sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40 dark:from-zinc-700/20 dark:to-zinc-800/10" />
|
||||
<div class="theme-transition-bg absolute -bottom-8 -left-8 h-20 w-20 rounded-full bg-gradient-to-tl from-zinc-200/30 to-zinc-300/20 blur-xl sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40 dark:from-zinc-700/20 dark:to-zinc-800/10" />
|
||||
<div class="theme-transition-bg absolute -right-8 -top-8 h-20 w-20 rounded-full bg-gradient-to-br from-zinc-200/30 to-zinc-300/20 blur-xl dark:from-zinc-700/20 dark:to-zinc-800/10 sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40" />
|
||||
<div class="theme-transition-bg absolute -bottom-8 -left-8 h-20 w-20 rounded-full bg-gradient-to-tl from-zinc-200/30 to-zinc-300/20 blur-xl dark:from-zinc-700/20 dark:to-zinc-800/10 sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40" />
|
||||
|
||||
<h2 class="theme-transition-color mb-3 text-center text-lg font-bold text-zinc-900 sm:mb-4 sm:text-xl md:mb-6 md:text-2xl lg:mb-8 lg:text-3xl dark:text-zinc-100">
|
||||
<h2 class="theme-transition-color mb-3 text-center text-lg font-bold text-zinc-900 dark:text-zinc-100 sm:mb-4 sm:text-xl md:mb-6 md:text-2xl lg:mb-8 lg:text-3xl">
|
||||
Popular Topics
|
||||
</h2>
|
||||
|
||||
@@ -114,23 +114,23 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count);
|
||||
{sortedTags.map((tag) => (
|
||||
<a
|
||||
href={`/topics/${tag.name}`}
|
||||
class="theme-transition-element theme-ripple group relative min-w-0 grow overflow-hidden rounded-md border border-zinc-200 transition-all duration-300 hover:scale-[1.03] hover:border-zinc-300 hover:shadow-md active:scale-95 sm:rounded-lg sm:hover:shadow-lg md:rounded-xl dark:border-zinc-800 dark:hover:border-zinc-700"
|
||||
class="theme-transition-element theme-ripple group relative min-w-0 flex-grow overflow-hidden rounded-md border border-zinc-200 transition-all duration-300 hover:scale-[1.03] hover:border-zinc-300 hover:shadow-md active:scale-95 dark:border-zinc-800 dark:hover:border-zinc-700 sm:rounded-lg sm:hover:shadow-lg md:rounded-xl"
|
||||
style={`--tag-hue: ${tag.hue};`}
|
||||
>
|
||||
<div class="theme-transition-bg absolute inset-0 bg-gradient-to-br from-zinc-50/90 to-zinc-100/90 opacity-100 transition-opacity group-hover:opacity-95 dark:from-zinc-800/90 dark:to-zinc-900/90" />
|
||||
|
||||
<div class="xxxs:px-2 xxs:px-2 xs:px-2 xxxs:py-2 xxs:py-2 xs:py-2 xxs:gap-2 relative flex w-full items-center gap-1.5 px-1.5 py-1.5 sm:px-3 sm:py-3 md:px-4 md:py-4">
|
||||
<div class="xxxs:w-6 xxxs:h-6 xxs:w-6 xxs:h-6 xs:w-7 xs:h-7 group-hover:bg-accent/20 dark:group-hover:bg-accent/20 group-hover:text-accent-dark dark:group-hover:text-accent-light theme-transition-all flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-zinc-700 shadow-xs transition-all duration-300 sm:h-8 sm:w-8 md:h-10 md:w-10 dark:bg-zinc-800 dark:text-zinc-300">
|
||||
<div class="xxxs:w-6 xxxs:h-6 xxs:w-6 xxs:h-6 xs:w-7 xs:h-7 group-hover:bg-accent/20 dark:group-hover:bg-accent/20 group-hover:text-accent-dark dark:group-hover:text-accent-light theme-transition-all flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-zinc-100 text-zinc-700 shadow-sm transition-all duration-300 dark:bg-zinc-800 dark:text-zinc-300 sm:h-8 sm:w-8 md:h-10 md:w-10">
|
||||
<span class="xxxs:text-xs xxs:text-xs xs:text-sm text-xs font-semibold sm:text-base md:text-lg">
|
||||
#
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1 overflow-hidden">
|
||||
<h3 class="xxxs:text-xs xxs:text-xs xs:text-xs theme-transition-color truncate text-[10px] font-bold break-words hyphens-auto text-zinc-900 transition-colors group-hover:text-zinc-700 sm:text-sm md:text-base dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<h3 class="xxxs:text-xs xxs:text-xs xs:text-xs theme-transition-color truncate hyphens-auto break-words text-[10px] font-bold text-zinc-900 transition-colors group-hover:text-zinc-700 dark:text-zinc-100 dark:group-hover:text-zinc-300 sm:text-sm md:text-base">
|
||||
{tag.name}
|
||||
</h3>
|
||||
<p class="xxxs:text-[9px] xxs:text-[9px] xs:text-[10px] theme-transition-color truncate text-[8px] text-zinc-500 sm:text-xs md:text-xs dark:text-zinc-400">
|
||||
<p class="xxxs:text-[9px] xxs:text-[9px] xs:text-[10px] theme-transition-color truncate text-[8px] text-zinc-500 dark:text-zinc-400 sm:text-xs md:text-xs">
|
||||
{tag.count} article{tag.count !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
@@ -554,8 +554,8 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
/* Prevent layout shifts */
|
||||
.grow {
|
||||
grow: 1;
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.min-w-0 {
|
||||
|
@@ -1,9 +1,7 @@
|
||||
/* Remove all the complex mobile menu styles and keep only what's necessary */
|
||||
@import 'tailwindcss';
|
||||
|
||||
/* Dark mode support for Tailwind CSS v4 */
|
||||
/* https://tailwindcss.com/docs/dark-mode */
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
@@ -16,7 +14,6 @@
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: 5rem;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -130,13 +127,13 @@
|
||||
/* Smooth hover transitions */
|
||||
a,
|
||||
button {
|
||||
transition: all 0.5s ease;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
a.hover:hover,
|
||||
a:hover,
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Smooth page transitions */
|
||||
.page-transition {
|
||||
|
@@ -3,7 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"target": "ES6",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
Reference in New Issue
Block a user