Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
71e2b0185b | |||
7f9fb4d2b9 | |||
8420c8dd58 | |||
fa6ed18edb | |||
30860fce1e | |||
b479e0e22c | |||
cf01ebcd3c | |||
df8ccf81c2
|
|||
073911c1b9 | |||
3eeea3dd8f | |||
43fea76778
|
|||
d64df6473a | |||
63a6a00817
|
|||
54759056b3 | |||
3cc9762e0d | |||
ef757c4a14
|
|||
176f92bf67 | |||
09d411dd68 | |||
54acfcb24d
|
|||
6f3b631862
|
|||
18cd240a9b | |||
bb4fe8ef37
|
|||
e0e3c1f61a
|
|||
0b5c6ae999 | |||
a20ba4ab43 | |||
550e7dfe52 | |||
03174cfb9d
|
|||
da50c1928c
|
|||
f1d1fe979e | |||
4d6019d0b0 | |||
7dd302b3d4
|
|||
8a8f2a6216
|
|||
97775f1ceb | |||
0a437a26f1 | |||
ba67b4d0e4 | |||
0bcfa9bed4
|
|||
ada95481f7
|
|||
7c9f4acc00
|
|||
0b7b87580a
|
|||
08f076e566
|
|||
26c27b9353 | |||
ce8b3a2e19 | |||
6d34c0d407 | |||
63607bbca3 | |||
745d2553a0
|
|||
8a19559cc7 | |||
42854db0fb
|
|||
7b72e3849b | |||
6a8dbb0c7c | |||
91fdf5a83f
|
|||
073f3a7916
|
|||
38202841ca | |||
0492922cce
|
|||
a17500835b | |||
2f8b97208c | |||
d6c30d5e5b
|
|||
a7ea9db3aa
|
|||
9134e78e8a | |||
2ca7d6705d | |||
5722e8c7a1 | |||
e39fd2acb8 | |||
0313fd54bc | |||
dbb0f6d7ff | |||
20669d9766 | |||
6b2e6353d1 | |||
6d112b52df | |||
ff17af604f | |||
32ea0989d7 | |||
e4ab7d134c | |||
5fad13655c | |||
8614d40a64 | |||
8c417b93b3 | |||
1d9519831b | |||
fa57f2e93f | |||
9e01002d4e | |||
cb52c169a3 | |||
3017668cd2 | |||
1972b3bc19 |
40
.gitea/workflows/process-repository.yaml
Normal file
40
.gitea/workflows/process-repository.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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
|
@@ -13,7 +13,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
renovate:
|
renovate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/renovatebot/renovate:40
|
container: ghcr.io/renovatebot/renovate:41
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
@@ -1,75 +0,0 @@
|
|||||||
name: tag-old-issues
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '@daily'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tag-old-issues:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Tag Old Issues
|
|
||||||
env:
|
|
||||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
INSTANCE_URL: ${{ vars.INSTANCE_URL }}
|
|
||||||
REPO_OWNER: ${{ github.repository_owner }}
|
|
||||||
REPO_NAME: ${{ github.repository_name }}
|
|
||||||
TAG_NAME: 'stale'
|
|
||||||
DAYS_OLD: 3
|
|
||||||
EXCLUDE_TAG_NAME: ''
|
|
||||||
REQUIRED_TAG: 'automerge'
|
|
||||||
run: |
|
|
||||||
# Install necessary tools
|
|
||||||
apt-get update && apt-get install -y jq curl
|
|
||||||
|
|
||||||
# --- Conditionally build the API URL ---
|
|
||||||
API_URL="${GITEA_INSTANCE_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues?state=open"
|
|
||||||
if [[ -n "${REQUIRED_TAG}" ]]; then
|
|
||||||
echo "Filtering for issues with the required tag: ${REQUIRED_TAG}"
|
|
||||||
# URL-encode the tag to handle special characters
|
|
||||||
ENCODED_TAG=$(jq -s -R -r @uri <<< "${REQUIRED_TAG}")
|
|
||||||
API_URL="${API_URL}&labels=${ENCODED_TAG}"
|
|
||||||
else
|
|
||||||
echo "No required tag specified. Checking all open issues."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fetch issues using the constructed URL
|
|
||||||
ISSUES=$(curl -s -X GET \
|
|
||||||
-H "Authorization: token ${BOT_TOKEN}" \
|
|
||||||
-H "Accept: application/json" \
|
|
||||||
"${API_URL}")
|
|
||||||
|
|
||||||
# Calculate the date ${DAYS_OLD} days ago in ISO 8601 format
|
|
||||||
OLDER_THAN_DATE=$(date -d "-${DAYS_OLD} days" -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
|
|
||||||
# Filter issues older than the specified date and without the exclusion tag
|
|
||||||
echo "$ISSUES" | jq -c '.[] | select(.created_at < "'"$OLDER_THAN_DATE"'")' | while read -r issue; do
|
|
||||||
ISSUE_NUMBER=$(echo "$issue" | jq -r '.number')
|
|
||||||
LABELS=$(echo "$issue" | jq -r '.labels[].name')
|
|
||||||
|
|
||||||
# Check if the issue has the exclusion tag
|
|
||||||
if ! echo "$LABELS" | grep -q -w "${EXCLUDE_TAG_NAME}"; then
|
|
||||||
echo "Tagging issue #${ISSUE_NUMBER} as ${TAG_NAME}"
|
|
||||||
|
|
||||||
# Get existing labels for the issue
|
|
||||||
EXISTING_LABELS=$(curl -s -X GET \
|
|
||||||
-H "Authorization: token ${BOT_TOKEN}" \
|
|
||||||
-H "Accept: application/json" \
|
|
||||||
"${INSTANCE_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${ISSUE_NUMBER}/labels" | jq -r '.[].name')
|
|
||||||
|
|
||||||
# Add the new tag to the list of existing labels
|
|
||||||
NEW_LABELS=$(echo -e "${EXISTING_LABELS}\n${TAG_NAME}" | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
|
||||||
|
|
||||||
# Update the issue with the new set of labels
|
|
||||||
curl -s -X PUT \
|
|
||||||
-H "Authorization: token ${BOT_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"labels\": $(echo "$NEW_LABELS" | jq -r 'map(select(. != ""))')}" \
|
|
||||||
"${INSTANCE_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${ISSUE_NUMBER}/labels"
|
|
||||||
else
|
|
||||||
echo "Skipping issue #${ISSUE_NUMBER} because it has the '${EXCLUDE_TAG_NAME}' tag."
|
|
||||||
fi
|
|
||||||
done
|
|
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22.16.x
|
node-version: 22.17.x
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
ARG REGISTRY=docker.io
|
ARG REGISTRY=docker.io
|
||||||
FROM ${REGISTRY}/node:22.16.0-alpine3.22 AS base
|
FROM ${REGISTRY}/node:22.17.0-alpine3.22 AS base
|
||||||
|
|
||||||
LABEL version="0.8.11"
|
LABEL version="0.9.0"
|
||||||
LABEL description="Astro based personal website"
|
LABEL description="Astro based personal website"
|
||||||
|
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Copyright (c) 2025 Lê Vĩnh Khang
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
|
12
package.json
12
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "site-profile",
|
"name": "site-profile",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.8.11",
|
"version": "0.9.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
"@astrojs/node": "^9.2.2",
|
"@astrojs/node": "^9.2.2",
|
||||||
"@astrojs/react": "^4.3.0",
|
"@astrojs/react": "^4.3.0",
|
||||||
"@astrojs/rss": "^4.0.12",
|
"@astrojs/rss": "^4.0.12",
|
||||||
"@directus/sdk": "^19.1.0",
|
"@directus/sdk": "^20.0.0",
|
||||||
"@tailwindcss/postcss": "^4.1.8",
|
"@tailwindcss/postcss": "^4.1.8",
|
||||||
"@tailwindcss/vite": "^4.1.8",
|
"@tailwindcss/vite": "^4.1.8",
|
||||||
"astro": "^5.9.2",
|
"astro": "^5.10.1",
|
||||||
"framer-motion": "^12.16.0",
|
"framer-motion": "^12.16.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
@@ -31,13 +31,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@typescript-eslint/parser": "8.34.0",
|
"@typescript-eslint/parser": "8.36.0",
|
||||||
"eslint": "9.28.0",
|
"eslint": "9.31.0",
|
||||||
"eslint-config-prettier": "10.1.5",
|
"eslint-config-prettier": "10.1.5",
|
||||||
"eslint-plugin-astro": "1.3.1",
|
"eslint-plugin-astro": "1.3.1",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.12",
|
"prettier-plugin-tailwindcss": "^0.6.12",
|
||||||
"typescript-eslint": "8.34.0"
|
"typescript-eslint": "8.36.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2113
pnpm-lock.yaml
generated
2113
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,40 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["config:recommended", "mergeConfidence:all-badges", ":rebaseStalePrs"],
|
"extends": [
|
||||||
"timezone": "US/Central",
|
"config:recommended",
|
||||||
"schedule": ["* */1 * * *"],
|
"mergeConfidence:all-badges",
|
||||||
"labels": [],
|
":rebaseStalePrs"
|
||||||
"prHourlyLimit": 0,
|
],
|
||||||
"prConcurrentLimit": 0,
|
"timezone": "US/Central",
|
||||||
"packageRules": []
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
38
src/components/DynamicIcon.tsx
Normal file
38
src/components/DynamicIcon.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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;
|
@@ -137,114 +137,113 @@ const socialLinks = [
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom section -->
|
<!-- Bottom section -->
|
||||||
<div class="theme-transition-all mt-12 border-t border-zinc-200 pt-8 dark:border-zinc-800">
|
<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">
|
<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">
|
<p class="theme-transition-color text-sm text-zinc-600 dark:text-zinc-400">
|
||||||
© {currentYear} All rights reserved.
|
© {currentYear} All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span class="theme-transition-color text-xs text-zinc-500 dark:text-zinc-400"
|
<span class="theme-transition-color text-xs text-zinc-500 dark:text-zinc-400"
|
||||||
>Built with</span
|
>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"
|
||||||
>
|
>
|
||||||
<a
|
<path
|
||||||
href="https://astro.build"
|
fill-rule="evenodd"
|
||||||
target="_blank"
|
clip-rule="evenodd"
|
||||||
rel="noopener noreferrer"
|
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"
|
||||||
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"
|
fill="currentColor"></path>
|
||||||
>
|
<path
|
||||||
<svg
|
fill-rule="evenodd"
|
||||||
class="mr-1 h-4 w-4 text-[#FF5D01] group-hover:animate-pulse"
|
clip-rule="evenodd"
|
||||||
viewBox="0 0 36 36"
|
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="none"
|
fill="currentColor"></path>
|
||||||
>
|
</svg>
|
||||||
<path
|
<span class="relative">
|
||||||
fill-rule="evenodd"
|
Astro
|
||||||
clip-rule="evenodd"
|
<span
|
||||||
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"
|
class="absolute bottom-0 left-0 h-0.5 w-0 bg-[#FF5D01] transition-all duration-300 group-hover:w-full"
|
||||||
fill="currentColor"></path>
|
></span>
|
||||||
<path
|
</span>
|
||||||
fill-rule="evenodd"
|
</a>
|
||||||
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>
|
</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>
|
</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>
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
import { FaJs, FaReact, FaNodeJs, FaPython } from 'react-icons/fa';
|
import DynamicIcon from '../components/DynamicIcon.tsx';
|
||||||
import { SiTypescript, SiAstro } from 'react-icons/si';
|
|
||||||
|
|
||||||
import directus from '../../lib/directus';
|
import directus from '../../lib/directus';
|
||||||
import { readSingleton, readItems } from '@directus/sdk';
|
import { readSingleton, readItems } from '@directus/sdk';
|
||||||
@@ -107,7 +106,7 @@ const skills = await directus.request(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Skills Section -->
|
<!-- Skills Section - Improved for mobile -->
|
||||||
<div class="theme-transition-all mb-16 sm:mb-20 md:mb-24">
|
<div class="theme-transition-all mb-16 sm:mb-20 md:mb-24">
|
||||||
<h2
|
<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 sm:mb-12 sm:text-3xl dark:text-zinc-100"
|
||||||
@@ -119,16 +118,13 @@ const skills = await directus.request(
|
|||||||
<!-- Main slider container -->
|
<!-- Main slider container -->
|
||||||
<div class="slider-track animate-slide flex">
|
<div class="slider-track animate-slide flex">
|
||||||
{
|
{
|
||||||
skills.map((skill, index) => (
|
[...skills, ...skills, ...skills].map((skill) => (
|
||||||
<div
|
<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">
|
||||||
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"
|
|
||||||
>
|
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<div class="mb-4 flex items-center justify-between sm:mb-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="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">
|
<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">
|
||||||
<skill.icon
|
<DynamicIcon name={skill.icon} />
|
||||||
/>
|
|
||||||
</div>
|
</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 sm:text-xl dark:text-zinc-100">
|
||||||
{skill.title}
|
{skill.title}
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
/* Remove all the complex mobile menu styles and keep only what's necessary */
|
/* Remove all the complex mobile menu styles and keep only what's necessary */
|
||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
/* Dark mode support for Tailwind CSS v4 */
|
||||||
|
/* https://tailwindcss.com/docs/dark-mode */
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
@@ -12,6 +16,7 @@
|
|||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
scroll-padding-top: 5rem;
|
scroll-padding-top: 5rem;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
Reference in New Issue
Block a user