Compare commits

..

1 Commits

Author SHA1 Message Date
539ef40813 chore(deps): update dependency @swup/astro to v1.8.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 47s
test-build / build (pull_request) Successful in 2m15s
2026-02-16 05:40:33 +00:00
42 changed files with 880 additions and 949 deletions

View File

@@ -60,7 +60,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
python-version: '3.14' python-version: '3.12'
- name: Install GuardDog - name: Install GuardDog
run: | run: |

View File

@@ -27,7 +27,7 @@ ENV SITE_URL=https://www.alexlebens.dev
ENV DIRECTUS_URL=https://directus.alexlebens.net ENV DIRECTUS_URL=https://directus.alexlebens.net
ENV PORT=4321 ENV PORT=4321
LABEL version="2.12.0" LABEL version="2.10.1"
LABEL description="Astro based personal website" LABEL description="Astro based personal website"
EXPOSE $PORT EXPOSE $PORT

View File

@@ -1,7 +1,7 @@
{ {
"name": "site-profile", "name": "site-profile",
"type": "module", "type": "module",
"version": "2.12.0", "version": "2.10.1",
"homepage": "https://www.alexlebens.dev", "homepage": "https://www.alexlebens.dev",
"bugs": { "bugs": {
"url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues",
@@ -39,16 +39,18 @@
"@iconify-json/pajamas": "^1.2.15", "@iconify-json/pajamas": "^1.2.15",
"@iconify-json/simple-icons": "^1.2.70", "@iconify-json/simple-icons": "^1.2.70",
"@playform/compress": "^0.2.1", "@playform/compress": "^0.2.1",
"@swup/astro": "^1.8.0", "@swup/astro": "^1.7.0",
"@tailwindcss/postcss": "^4.1.18", "@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/unist": "^3.0.3", "@types/unist": "^3.0.3",
"astro": "^5.17.2", "astro": "^5.17.2",
"astro-compressor": "^1.2.0",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"marked": "^17.0.2", "marked": "^17.0.2",
"marked-shiki": "^1.2.1", "marked-shiki": "^1.2.1",
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"motion": "^12.34.0",
"preline": "^4.0.1", "preline": "^4.0.1",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
@@ -60,12 +62,13 @@
"ultrahtml": "^1.6.0" "ultrahtml": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint-react/eslint-plugin": "^2.13.0", "@eslint-react/eslint-plugin": "^2.12.4",
"@tailwindcss/forms": "^0.5.11", "@tailwindcss/forms": "^0.5.11",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"astro-icon": "^1.1.5",
"eslint": "^10.0.0", "eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-astro": "^1.6.0", "eslint-plugin-astro": "^1.5.0",
"eslint-plugin-format": "^1.4.0", "eslint-plugin-format": "^1.4.0",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",

556
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,10 @@ import { getImage } from 'astro:assets';
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus'; import directus from '@lib/directus';
import { SEO } from '@/config';
import brandSrc from '@images/brand_logo.png'; import brandSrc from '@images/brand_logo.png';
import faviconSvgSrc from '@images/favicon_icon.svg'; import faviconSvgSrc from '@images/favicon_icon.svg';
import faviconSrc from '@images/favicon_icon.png'; import faviconSrc from '@images/favicon_icon.png';
import { SEO } from '@/config';
interface Props { interface Props {
title: string; title: string;
@@ -19,7 +18,6 @@ interface Props {
} }
const canonicalURL = Astro.url.href; const canonicalURL = Astro.url.href;
let { let {
title, title,
description, description,
@@ -29,14 +27,14 @@ let {
structuredData = SEO.structuredData, structuredData = SEO.structuredData,
} = Astro.props; } = Astro.props;
const global = await directus.request(readSingleton('site_global'));
let card = 'summary_large_image'; let card = 'summary_large_image';
if (!ogImage) { if (!ogImage) {
ogImage = brandSrc; ogImage = brandSrc;
card = 'summary'; card = 'summary';
} }
const global = await directus.request(readSingleton('site_global'));
const faviconSvg = await getImage({ src: faviconSvgSrc, format: 'svg' }); const faviconSvg = await getImage({ src: faviconSvgSrc, format: 'svg' });
const appleTouchIcon = await getImage({ src: faviconSrc, width: 180, height: 180, format: 'png' }); const appleTouchIcon = await getImage({ src: faviconSrc, width: 180, height: 180, format: 'png' });
const socialImageRes = await getImage({ src: ogImage, width: 1200, height: 600 }); const socialImageRes = await getImage({ src: ogImage, width: 1200, height: 600 });
@@ -64,12 +62,12 @@ if (!socialImage.startsWith('http')) {
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#facc15" /> <meta name="theme-color" content="#facc15" />
<meta name="robots" content="index, follow" />
<!-- Open Graph --> <!-- Open Graph -->
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" /> <meta property="og:locale" content="en_US" />
<meta property="og:url" content={Astro.url} /> <meta property="og:url" content={Astro.url} />
<meta property="og:type" content="website" />
<meta property="og:title" content={ogTitle} /> <meta property="og:title" content={ogTitle} />
<meta property="og:site_name" content={global.name} /> <meta property="og:site_name" content={global.name} />
<meta property="og:description" content={ogDescription} /> <meta property="og:description" content={ogDescription} />
@@ -78,10 +76,17 @@ if (!socialImage.startsWith('http')) {
<meta content="600" property="og:image:height" /> <meta content="600" property="og:image:height" />
<meta content="image/png" property="og:image:type" /> <meta content="image/png" property="og:image:type" />
<!-- Twitter -->
<meta property="twitter:card" content={card} />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:domain" content={Astro.url} />
<meta property="twitter:title" content={ogTitle} />
<meta property="twitter:description" content={ogDescription} />
<meta property="twitter:image" content={socialImage} />
<!-- Links --> <!-- Links -->
<link href={canonicalURL} rel="canonical" /> <link href={canonicalURL} rel="canonical" />
<link rel="sitemap" href="/sitemap-index.xml" /> <link rel="sitemap" href="/sitemap-index.xml" />
<link rel="alternate" type="application/rss+xml" title={title} href="/rss.xml" />
<!--<link href="/manifest.json" rel="manifest" />--> <!--<link href="/manifest.json" rel="manifest" />-->
<link href="/favicon.ico" rel="icon" sizes="any" type="image/x-icon" /> <link href="/favicon.ico" rel="icon" sizes="any" type="image/x-icon" />
<link href={faviconSvg.src} rel="icon" type="image/svg+xml" sizes="any" /> <link href={faviconSvg.src} rel="icon" type="image/svg+xml" sizes="any" />

View File

@@ -1,80 +1,92 @@
--- ---
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus';
import BrandLogo from '@components/ui/logos/BrandLogo.astro'; import BrandLogo from '@components/ui/logos/BrandLogo.astro';
import Image from '@components/ui/images/Image.astro'; import Image from '@components/ui/images/Image.astro';
import directus from '@lib/directus';
import { NavigationLinks, FooterLinks } from '@/config'; import { NavigationLinks, FooterLinks } from '@/config';
import footerImg from '@images/flowers.png'; import footerImg from '@images/flowers.png';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
--- ---
<footer <footer
class="bg-background-accent w-full overflow-hidden" class="w-full overflow-hidden bg-stone-300/40 dark:bg-stone-800/20"
transition:animate="none" transition:animate="none"
> >
<div class="relative px-4 sm:px-6 pt-16 pb-12"> <div class="relative px-4 pt-16 pb-12 sm:px-6">
<div class="max-w-340 mx-auto"> <div class="mx-auto max-w-340">
<div class="grid grid-cols-1 md:grid-cols-12 gap-10"> <div class="grid grid-cols-1 gap-10 md:grid-cols-12">
<!-- Brand section --> <!-- Brand section -->
<div class="col-span-1 md:col-span-3"> <div class="col-span-1 md:col-span-3">
<a href="/" class="group inline-block"> <a href="/" class="group inline-block">
<div class="flex items-center"> <div class="flex items-center">
<div class="mx-auto aspect-square overflow-hidden"> <div class="mx-auto aspect-square overflow-hidden rounded-lg">
<BrandLogo class="rounded-lg max-h-10 max-w-10"/> <BrandLogo class="max-h-10 max-w-10 rounded-full" />
</div> </div>
<span class="text-header text-lg lg:text-2xl font-semibold leading-tight tracking-tight text-balance ml-3">
<span class="ml-3 text-xl font-bold text-neutral-800 dark:text-neutral-200">
{global.name} {global.name}
</span> </span>
</div> </div>
</a> </a>
<p class="text-primary text-sm lg:text-base text-pretty leading-relaxed mt-4">
<p class="mt-4 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
{global.about} {global.about}
</p> </p>
</div> </div>
<!-- Left links --> <!-- Left links -->
<div class="col-span-1 md:col-span-2"> <div class="col-span-1 md:col-span-2">
<h3 class="relative inline-block text-header after:bg-main text-sm uppercase font-semibold tracking-wider pb-2 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:content-['']"> <h3
Site class="after:bg-steel dark:after:bg-bermuda relative inline-block pb-2 text-sm font-semibold tracking-wider text-neutral-800 uppercase after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:content-[''] dark:text-neutral-100"
>
Blog
</h3> </h3>
<ul class="mt-4 space-y-3"> <ul class="mt-4 space-y-3">
{NavigationLinks.map((link) => ( {
NavigationLinks.map((link) => (
<li> <li>
<a <a
href={link.url} href={link.url}
class="inline-flex items-center text-secondary hover:text-secondary-hover text-base transition-all duration-300 overflow-hidden" class="group flex items-center text-base text-neutral-600 transition-colors hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200"
> >
{link.name} <span class="relative inline-block overflow-hidden">
<span class="relative z-10">{link.name}</span>
</span>
</a> </a>
</li> </li>
))} ))
}
</ul> </ul>
</div> </div>
<!-- Right links --> <!-- Right links -->
<div class="col-span-1 md:col-span-3"> <div class="col-span-1 md:col-span-3">
<h3 class="relative inline-block text-header after:bg-main text-sm uppercase font-semibold tracking-wider pb-2 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:content-['']"> <h3
class="after:bg-steel dark:after:bg-bermuda relative inline-block pb-2 text-sm font-semibold tracking-wider text-neutral-800 uppercase after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:content-[''] dark:text-neutral-100"
>
Other Other
</h3> </h3>
<ul class="mt-4 space-y-3"> <ul class="mt-4 space-y-3">
{FooterLinks.map((link) => ( {
FooterLinks.map((link) => (
<li> <li>
<a <a
href={link.url} href={link.url}
class="inline-flex items-center text-secondary hover:text-secondary-hover text-base transition-all duration-300 overflow-hidden" class="group flex items-center text-base text-neutral-600 transition-colors hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200"
> >
{link.name} <span class="relative inline-block overflow-hidden">
<span class="relative z-10">{link.name}</span>
</span>
</a> </a>
</li> </li>
))} ))
}
</ul> </ul>
</div> </div>
<!-- Right image --> <!-- Right image -->
<div class="flex justify-center col-span-4 mt-10 md:mt-0"> <div class="col-span-3 mt-10 flex justify-center md:mt-0">
<div class="md:block max-h-115 max-w-55 -mt-10 scale-80 hidden"> <div class="-mt-10 hidden max-h-[460px] max-w-[220px] scale-80 md:block">
<Image <Image
src={footerImg} src={footerImg}
alt={global.footer_image_alt} alt={global.footer_image_alt}
@@ -90,36 +102,37 @@ const currentYear = new Date().getFullYear();
</div> </div>
</div> </div>
<!-- Bottom section --> <!-- Bottom section -->
<div class="border-t border-neutral-400/30 dark:border-neutral-600/50 pt-8 mt-12"> <div class="mt-12 border-t border-neutral-400/30 pt-8 dark:border-neutral-600/50">
<div class="flex flex-col md:flex-row items-center justify-between gap-4"> <div class="flex flex-col items-center justify-between gap-4 md:flex-row">
<p class="text-secondary text-sm"> <p class="text-sm text-neutral-600 dark:text-neutral-400">
&copy; {currentYear} All rights reserved. &copy; {currentYear} All rights reserved.
</p> </p>
<div class="flex items-center"> <div class="flex items-center">
<span class="text-secondary text-sm"> <p class="text-xs text-neutral-500 dark:text-neutral-400">
Weather provided by Weather provided by
</span> </p>
<a <a
href="https://open-meteo.com/" href="https://open-meteo.com/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="group inline-flex items-center text-secondary hover:text-secondary-hover text-sm transition-all duration-300" class="group inline-flex items-center text-xs text-neutral-600 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
> >
<span class="relative underline ml-1"> <span class="relative ml-1">
Open-Meteo. Open-Meteo.
</span> </span>
</a> </a>
<div class="ml-4"/>
<span class="text-secondary text-sm"> <div class="ml-4"></div>
Built with
</span> <span class="text-xs text-neutral-500 dark:text-neutral-400">Built with </span>
<a <a
href="https://astro.build" href="https://astro.build"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="group inline-flex items-center text-secondary hover:text-secondary-hover text-sm transition-all duration-300" class="group inline-flex items-center text-xs text-neutral-600 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
> >
<span class="relative underline ml-1"> <span class="relative ml-1">
Astro. Astro.
</span> </span>
</a> </a>

View File

@@ -9,31 +9,31 @@ const currentPath = pathname.slice(1);
<header <header
id="nav" id="nav"
class="fixed flex flex-wrap md:flex-nowrap md:justify-start inset-x-0 top-0 w-full z-50" class="sticky inset-x-0 top-4 z-50 flex w-full flex-wrap text-sm transition-none md:flex-nowrap md:justify-start"
> >
<div class="bg-linear-to-b from-background from-65% to-transparent to-90% absolute top-0 bottom-0 left-0 w-full h-36 z-0"/>
<nav <nav
class="nav-base relative md:flex md:items-center md:justify-between rounded-[36px] w-full px-4 mx-2 py-3 mt-4" class="relative mx-2 w-full rounded-[36px] border border-neutral-100 bg-neutral-100 px-4 py-3 md:flex md:items-center md:justify-between md:px-6 lg:px-8 dark:border-neutral-700/40 dark:bg-neutral-800/80"
aria-label="Global" aria-label="Global"
> >
<div class="flex items-center justify-between ml-0"> <div class="flex items-center justify-between">
<a <a
class="flex-none rounded-full h-10.5" class="h-[42px] flex-none rounded-lg text-xl font-bold ring-neutral-500 outline-none focus-visible:ring dark:ring-neutral-200 dark:focus:outline-none"
href="/" href="/"
aria-label="Brand" aria-label="Brand"
> >
<BrandLogo class="h-full w-auto rounded-full object-cover" /> <BrandLogo class="h-full w-auto rounded-full object-cover" />
</a> </a>
<div class="md:hidden mr-auto ml-4">
<div class="ml-auto md:hidden">
<button <button
type="button" type="button"
class="hs-collapse-toggle flex items-center justify-center text-secondary text-sm font-bold hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded-full transition duration-300 disabled:pointer-events-none disabled:opacity-50 h-8 w-8" class="hs-collapse-toggle flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold text-neutral-600 transition duration-300 hover:bg-neutral-200 disabled:pointer-events-none disabled:opacity-50 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:focus:outline-none"
data-hs-collapse="#navbar-collapse-with-animation" data-hs-collapse="#navbar-collapse-with-animation"
aria-controls="navbar-collapse-with-animation" aria-controls="navbar-collapse-with-animation"
aria-label="Toggle navigation" aria-label="Toggle navigation"
> >
<svg <svg
class="hs-collapse-open:hidden shrink-0 h-5 w-5" class="hs-collapse-open:hidden h-5 w-5 shrink-0"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -48,7 +48,7 @@ const currentPath = pathname.slice(1);
<line x1="3" x2="21" y1="18" y2="18"></line> <line x1="3" x2="21" y1="18" y2="18"></line>
</svg> </svg>
<svg <svg
class="hs-collapse-open:block shrink-0 h-5 w-5 hidden" class="hs-collapse-open:block hidden h-5 w-5 shrink-0"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -63,33 +63,33 @@ const currentPath = pathname.slice(1);
</svg> </svg>
</button> </button>
</div> </div>
<div class="md:hidden ml-2 mr-2">
<span class="">
<ThemeToggleButton />
</span>
</div> </div>
</div>
<div class="flex md:flex-row items-center justify-between">
<div <div
id="navbar-collapse-with-animation" id="navbar-collapse-with-animation"
class="hs-collapse grow basis-full md:block transition-all duration-300 ml-2 mb-2 md:mb-0 hidden overflow-hidden md:overflow-visible" class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block md:overflow-visible"
> >
<div class="flex flex-col md:flex-row md:items-center md:justify-end gap-x-0 md:gap-x-4 lg:gap-x-7 gap-y-4 md:gap-y-0 md:ps-7 mr-2 mt-5 md:mt-0"> <div
{NavigationLinks.map((item) => { class="mt-5 flex flex-col gap-x-0 gap-y-4 md:mt-0 md:flex-row md:items-center md:justify-end md:gap-x-4 md:gap-y-0 md:ps-7 lg:gap-x-7"
>
{
NavigationLinks.map((item) => {
const isActive = currentPath === (item.url === '/' ? '' : item.url.slice(1)); const isActive = currentPath === (item.url === '/' ? '' : item.url.slice(1));
return ( return (
<a <a
href={item.url} href={item.url}
class={`text-sm font-medium ${isActive ? 'text-active' : 'text-secondary hover:text-secondary-hover'}`} class={`text-sm font-medium ${
isActive
? 'text-orange-500 dark:text-orange-300'
: 'text-neutral-600 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100'
}`}
> >
{item.name} {item.name}
</a> </a>
); );
})} })
</div> }
</div> <span class="md:inline-block">
<div class="hidden md:flex ml-2">
<span class="">
<ThemeToggleButton /> <ThemeToggleButton />
</span> </span>
</div> </div>

View File

@@ -0,0 +1,61 @@
---
import { Icon } from 'astro-icon/components';
import type { Post } from '@lib/directusTypes';
import { getDirectusImageURL } from '@lib/directusFunctions';
import Image from '@components/ui/images/Image.astro';
import { formatDate } from '@support/time';
interface Props {
post: Post;
}
const { post } = Astro.props;
const baseClasses = 'group group-hover smooth-reveal-cards rounded-xl flex flex-col';
const borderClasses = 'border border-stone-200/50 dark:border-stone-700/50';
const bgColorClasses =
'bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
const shadowClasses = 'shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg';
---
<div class={`${baseClasses}`}>
<a
class={`rounded-xl duration-300 transition-all ${borderClasses} ${shadowClasses} ${bgColorClasses}`}
href={`/blog/${post.slug}/`}
data-astro-prefetch
>
<div
class="relative w-full flex-shrink-0 overflow-hidden rounded-t-xl before:absolute before:inset-x-0 before:z-[1] before:size-full"
>
<Image
class="h-auto w-full rounded-t-xl"
src={getDirectusImageURL(post.image)}
alt={post.image_alt}
draggable="false"
loading="eager"
format="webp"
width="800"
height="460"
/>
</div>
<div class="rounded-xl p-4 md:p-5">
<h3 class="text-xl font-bold text-neutral-600 dark:text-neutral-200">
{post.title}
</h3>
<div
class="group-hover:text-steel dark:group-hover:text-bermuda transition-text relative z-10 mx-auto flex min-h-[44px] items-center font-medium text-neutral-600 decoration-2 duration-300 sm:mx-0 sm:mt-4 dark:text-neutral-400"
>
<span class="relative inline-block overflow-hidden"> Read more </span>
<Icon
name="mdi:keyboard-arrow-right"
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
/>
<p class="ml-auto text-sm text-neutral-600 dark:text-neutral-400">
{formatDate(post.published_date)}
</p>
</div>
</div>
</a>
</div>

View File

@@ -8,30 +8,37 @@ interface Props {
} }
const { slug, title, description, count, publishDate } = Astro.props; const { slug, title, description, count, publishDate } = Astro.props;
const baseClasses =
'group group-hover rounded-xl flex h-full min-h-[220px] cursor-pointer flex-col overflow-hidden';
const bgColorClasses =
'bg-neutral-100/60 dark:bg-neutral-800/60 hover:bg-neutral-100 dark:hover:bg-neutral-800/90 ';
--- ---
<div class="smooth-reveal-cards group h-full"> <a class={`rounded-xl`} href={`/categories/${slug}/`} data-astro-prefetch="false">
<a <div class={`${baseClasses}`}>
class="card-base flex flex-col h-full min-h-55" <div
href={`/categories/${slug}/`} class={`relative min-h-0 flex-grow overflow-hidden transition-all duration-300 ${bgColorClasses}`}
data-astro-prefetch
> >
<div class="relative grow overflow-hidden">
<div class="absolute inset-1 flex flex-col p-3 md:p-4 lg:p-5"> <div class="absolute inset-1 flex flex-col p-3 md:p-4 lg:p-5">
<div class="overflow-hidden"> <div class="overflow-hidden">
<h3 class="card-text-title-major card-hover-text-title whitespace-nowrap mb-4"> <h2
class="group-hover:text-steel dark:group-hover:text-bermuda transition-text mb-4 text-4xl font-extrabold tracking-tight text-balance whitespace-nowrap text-neutral-800 duration-300 dark:text-neutral-200"
>
{title} {title}
</h3> </h2>
<p class="card-text-description mb-4"> <p class="mb-4 font-light text-neutral-600 sm:text-lg dark:text-neutral-400">
{description} {description}
</p> </p>
</div> </div>
<div class="card-text-description flex items-center justify-between text-xs mt-auto pt-1 md:pt-2"> <div
class="mt-auto flex items-center justify-between pt-1 text-xs text-neutral-600 md:pt-2 dark:text-neutral-300"
>
<span class="inline-flex items-center"> <span class="inline-flex items-center">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="12"
height="16" height="12"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -44,8 +51,8 @@ const { slug, title, description, count, publishDate } = Astro.props;
<span class="inline-flex items-center"> <span class="inline-flex items-center">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="12"
height="16" height="12"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -59,5 +66,5 @@ const { slug, title, description, count, publishDate } = Astro.props;
</div> </div>
</div> </div>
</div> </div>
</a>
</div> </div>
</a>

View File

@@ -0,0 +1,44 @@
---
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import Image from '@components/ui/images/Image.astro';
interface Props {
title: string;
subTitle: string;
btnExists?: boolean;
btnTitle?: string;
btnURL?: string;
img: any;
imgAlt: any;
}
const { title, subTitle, btnExists, btnTitle, btnURL, img, imgAlt } = Astro.props;
---
<section
class="mx-auto max-w-[85rem] items-center gap-8 px-4 py-10 sm:px-6 sm:py-16 md:grid md:grid-cols-2 lg:grid lg:grid-cols-2 lg:px-8 lg:py-14 xl:gap-16 2xl:max-w-full"
>
<Image
class="h-full w-full rounded-xl object-cover sm:max-h-[320px] md:max-h-[360px]"
src={img}
alt={imgAlt}
draggable="false"
loading="lazy"
width="850"
height="420"
/>
<div class="mt-4 md:mt-0">
<h2
class="mb-4 text-4xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
>
{title}
</h2>
<p
class="mb-4 max-w-prose font-light text-pretty text-neutral-600 sm:text-lg dark:text-neutral-300"
>
{subTitle}
</p>
{btnExists ? <GoLinkPrimaryButton title={btnTitle} url={btnURL} /> : null}
</div>
</section>

View File

@@ -0,0 +1,29 @@
---
import type { Post } from '@lib/directusTypes';
import BlogCard from '@components/blog/BlogCard.astro';
interface Props {
posts: Post[];
}
const { posts } = Astro.props;
---
<section class="mx-auto mb-10 max-w-[85rem] px-4 py-8 sm:px-6 lg:px-8 2xl:max-w-full">
<div class="text-left">
<h2
id="recent-articles"
class="smooth-reveal-2 mb-10 text-5xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
>
Recent Posts
</h2>
</div>
<div class="flex flex-col md:flex-row md:space-x-12 lg:space-x-16">
<div class="w-full">
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{posts.map((b) => <BlogCard post={b} />)}
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,87 @@
---
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import Image from '@components/ui/images/Image.astro';
interface Props {
title: string;
subTitle: string;
btnExists?: boolean;
btnTitle?: string;
btnURL?: string;
single?: boolean;
imgOne?: any;
imgOneAlt?: any;
imgTwo?: any;
imgTwoAlt?: any;
}
const {
title,
subTitle,
btnExists,
btnTitle,
btnURL,
single,
imgOne,
imgOneAlt,
imgTwo,
imgTwoAlt,
} = Astro.props;
---
<section
class="mx-auto max-w-[85rem] items-center gap-16 px-4 py-10 sm:px-6 lg:grid lg:grid-cols-2 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div>
<h2
class="mb-4 text-4xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
>
{title}
</h2>
<p
class="mb-4 max-w-prose font-light text-pretty text-neutral-600 sm:text-lg dark:text-neutral-400"
>
{subTitle}
</p>
{btnExists ? <GoLinkPrimaryButton title={btnTitle} url={btnURL} /> : null}
</div>
{
single ? (
<div class="mt-8">
<Image
class="w-full rounded-lg"
src={imgOne}
alt={imgOneAlt}
format="webp"
loading="lazy"
width="850"
height="420"
/>
</div>
) : (
<div class="mt-8 grid grid-cols-2 gap-4">
<Image
class="w-full rounded-xl"
src={imgOne}
alt={imgOneAlt}
draggable="false"
format="webp"
loading="lazy"
width="400"
height="230"
/>
<Image
class="mt-4 w-full rounded-xl lg:mt-10"
src={imgTwo}
alt={imgTwoAlt}
draggable="false"
format="webp"
loading="lazy"
width="400"
height="230"
/>
</div>
)
}
</section>

View File

@@ -0,0 +1,44 @@
---
import type { Post } from '@lib/directusTypes';
import { getDirectusImageURL } from '@lib/directusFunctions';
import BlogLeftSection from '@components/blog/BlogLeftSection.astro';
import BlogRightSection from '@components/blog/BlogRightSection.astro';
interface Props {
posts: Post[];
}
const { posts } = Astro.props;
---
<section class="smooth-reveal">
{
posts.map((b, index) =>
index % 2 === 0 ? (
<BlogLeftSection
title={b.title}
subTitle={b.description}
btnExists={true}
btnTitle="Read More"
btnURL={`/blog/${b.slug}`}
img={getDirectusImageURL(b.image)}
imgAlt={b.image_alt}
/>
) : (
<BlogRightSection
title={b.title}
subTitle={b.description}
btnExists={true}
btnTitle="Read More"
btnURL={`/blog/${b.slug}`}
single={!b.image_second}
imgOne={getDirectusImageURL(b.image)}
imgOneAlt={b.image_alt}
imgTwo={getDirectusImageURL(b?.image_second)}
imgTwoAlt={b?.image_second_alt}
/>
)
)
}
</section>

View File

@@ -2,10 +2,11 @@
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components';
interface Props { interface Props {
title?: string;
url?: string; url?: string;
} }
const { url } = Astro.props; const { title, url } = Astro.props;
--- ---
<a <a

View File

@@ -1,5 +1,11 @@
--- ---
import Icon from '@components/ui/icons/icon.astro'; import Icon from '@components/ui/icons/icon.astro';
interface Props {
noArrow?: boolean;
}
const { noArrow } = Astro.props;
--- ---
<button <button
@@ -8,7 +14,7 @@ import Icon from '@components/ui/icons/icon.astro';
data-astro-prefetch data-astro-prefetch
> >
<div class="button-text-title flex relative items-center text-center"> <div class="button-text-title flex relative items-center text-center">
<Icon name="arrowLeft" /> {noArrow ? null : <Icon name="arrowLeft" />}
<span class="ml-2"> <span class="ml-2">
Go Back Go Back
</span> </span>

View File

@@ -33,7 +33,8 @@ const socialPlatforms: SocialPlatform[] = [
--- ---
<div class="inline-flex items-center gap-x-2"> <div class="inline-flex items-center gap-x-2">
{socialPlatforms.map((platform) => ( {
socialPlatforms.map((platform) => (
<a <a
class="button-base-hidden group inline-flex rounded-lg gap-x-2" class="button-base-hidden group inline-flex rounded-lg gap-x-2"
href={platform.url} href={platform.url}
@@ -42,11 +43,9 @@ const socialPlatforms: SocialPlatform[] = [
title={`Share on ${platform.name}`} title={`Share on ${platform.name}`}
> >
<div class="button-text-title-hidden flex relative items-center text-center"> <div class="button-text-title-hidden flex relative items-center text-center">
<Icon <Icon name={platform.svg} class="h-5 w-5" />
name={platform.svg}
class="h-5 w-5"
/>
</div> </div>
</a> </a>
))} ))
}
</div> </div>

View File

@@ -1,58 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import type { Post } from '@lib/directusTypes';
import Image from '@components/ui/images/Image.astro';
import { getDirectusImageURL } from '@lib/directusFunctions';
interface Props {
post: Post;
}
const { post } = Astro.props;
---
<div class="smooth-reveal-cards group flex flex-col">
<a
class="card-base border-none!"
href={`/blog/${post.slug}/`}
data-astro-prefetch
>
<div class="relative shrink-0 rounded-t-xl w-full overflow-hidden before:absolute before:inset-x-0 before:z-1 before:size-full">
<Image
class="rounded-t-xl h-auto w-full"
src={getDirectusImageURL(post.image)}
alt={post.image_alt}
draggable="false"
loading="eager"
format="webp"
/>
</div>
<div class="rounded-xl p-4 md:p-5">
<h3 class="card-text-title text-xl">
{post.title}
</h3>
<div class="ml-6 flex">
<div class="relative inline-block w-full">
<div class="card-text-title card-hover-text-title flex relative items-center mx-auto min-h-11 sm:mx-0 sm:mt-4">
<span class="relative inline-block overflow-hidden ml-2">
Read more
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="translate-y-0.5 transition duration-300 group-hover:translate-x-1"
/>
<p class="card-text-description text-sm ml-auto">
{new Date(post.published_date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
})}
</p>
</div>
</div>
</div>
</div>
</a>
</div>

View File

@@ -1,8 +1,6 @@
--- ---
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components';
import Logo from '@components/ui/logos/Logo.astro'; import Logo from '@components/ui/logos/Logo.astro';
import { getDirectusImageURL } from '@lib/directusFunctions';
interface Props { interface Props {
topic: string; topic: string;
@@ -14,8 +12,7 @@ interface Props {
logoIcon?: string; logoIcon?: string;
} }
const { topic, area, date, url, logoUrlLight, logoIcon } = Astro.props; const { topic, area, date, url, logoUrlLight, logoUrlDark, logoIcon } = Astro.props;
const logoUrlDark = Astro.props.logoUrlDark || logoUrlLight;
--- ---
<div class="smooth-reveal group flex flex-col"> <div class="smooth-reveal group flex flex-col">
@@ -28,8 +25,8 @@ const logoUrlDark = Astro.props.logoUrlDark || logoUrlLight;
{logoUrlLight ? ( {logoUrlLight ? (
<div class="card-hover-icon-scale mr-5"> <div class="card-hover-icon-scale mr-5">
<Logo <Logo
srcLight={getDirectusImageURL(logoUrlLight)} srcLight={logoUrlLight}
srcDark={getDirectusImageURL(logoUrlDark!)} srcDark={logoUrlDark}
alt={`Logo of ${topic}`} alt={`Logo of ${topic}`}
/> />
</div> </div>

View File

@@ -9,11 +9,13 @@ interface Props {
} }
const { title, description, url, icon } = Astro.props; const { title, description, url, icon } = Astro.props;
const sizeClasses = 'h-30 w-100 md:w-[300px]';
--- ---
<div class="smooth-reveal-2 group flex flex-col"> <div class="smooth-reveal-2 group flex flex-col">
<a <a
class="card-base flex items-center h-30 w-100 md:w-75" class={`card-base flex items-center ${sizeClasses}`}
href={url} href={url}
data-astro-prefetch data-astro-prefetch
> >
@@ -27,9 +29,9 @@ const { title, description, url, icon } = Astro.props;
<span class="card-text-title card-hover-text-title block text-lg"> <span class="card-text-title card-hover-text-title block text-lg">
{title} {title}
</span> </span>
<p class="card-text-description block mt-1"> <span class="card-text-description block mt-1">
{description} {description}
</p> </span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -39,9 +39,9 @@ const visitClass = visitSource ? 'card-hover-text-gitea' : 'card-hover-text-titl
<span class="card-text-title block text-lg"> <span class="card-text-title block text-lg">
{title} {title}
</span> </span>
<p class="card-text-description block mt-1"> <span class="card-text-description block mt-1">
{description} {description}
</p> </span>
</div> </div>
</div> </div>
{highlights && ( {highlights && (
@@ -55,7 +55,7 @@ const visitClass = visitSource ? 'card-hover-text-gitea' : 'card-hover-text-titl
)} )}
<div class="ml-6 flex"> <div class="ml-6 flex">
<div class="relative inline-block"> <div class="relative inline-block">
<div class={`card-text-title ${visitClass} flex relative items-center font-semibold text-md min-h-11 mx-auto sm:mx-0 sm:mt-4`}> <div class={`card-text-title ${visitClass} flex relative items-center mx-auto min-h-11 font-semibold text-md sm:mx-0 sm:mt-4`}>
{visitSource && <Icon name="pajamas:gitea" />} {visitSource && <Icon name="pajamas:gitea" />}
<span class="relative inline-block overflow-hidden ml-2"> <span class="relative inline-block overflow-hidden ml-2">
{visitText} {visitText}

View File

@@ -1,55 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import Image from '@components/ui/images/Image.astro';
import { getDirectusImageURL } from '@lib/directusFunctions';
interface Props {
title: string;
subTitle: string;
url: string;
img: string;
imgAlt: string;
}
const { title, subTitle, url, img, imgAlt } = Astro.props;
---
<div class="smooth-reveal group">
<a
class="card-base-hidden md:grid md:grid-cols-2 lg:grid lg:grid-cols-2 items-center gap-8 xl:gap-16 max-w-340 2xl:max-w-full px-4 sm:px-6 lg:px-8 py-10 sm:py-16 lg:py-14 mx-auto"
href={url}
data-astro-prefetch
>
<div>
<Image
class="rounded-xl w-full h-full sm:max-h-80 md:max-h-90 object-cover"
src={getDirectusImageURL(img)}
alt={imgAlt}
draggable="false"
loading="lazy"
width="850"
height="420"
/>
</div>
<div>
<h2 class="card-text-header mb-4">
{title}
</h2>
<p class="card-text-title font-light text-pretty sm:text-lg max-w-prose mb-4">
{subTitle}
</p>
<div class="button-base button-bg-teal inline-flex rounded-lg gap-x-2">
<div class="button-text-title flex relative items-center text-center">
<span class="mr-2">
Read More
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="button-hover-arrow"
/>
</div>
</div>
</div>
</a>
</div>

View File

@@ -1,83 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import Image from '@components/ui/images/Image.astro';
import { getDirectusImageURL } from '@lib/directusFunctions';
interface Props {
title: string;
subTitle: string;
url: string;
single?: boolean;
imgOne: any;
imgOneAlt: any;
imgTwo?: any;
imgTwoAlt?: any;
}
const { title, subTitle, url, single, imgOne, imgOneAlt, imgTwo, imgTwoAlt } = Astro.props;
---
<div class="smooth-reveal group">
<a
class="card-base-hidden items-center lg:grid lg:grid-cols-2 gap-16 max-w-340 2xl:max-w-full px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto"
href={url}
data-astro-prefetch
>
<div>
<h2 class="card-text-header mb-4">
{title}
</h2>
<p class="card-text-title font-light text-pretty sm:text-lg max-w-prose mb-4">
{subTitle}
</p>
<div class="button-base button-bg-teal inline-flex rounded-lg gap-x-2">
<div class="button-text-title flex relative items-center text-center">
<span class="mr-2">
Read More
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="button-hover-arrow"
/>
</div>
</div>
</div>
{single ? (
<div>
<Image
class="rounded-xl w-full"
src={getDirectusImageURL(imgOne)}
alt={imgOneAlt}
format="webp"
loading="lazy"
width="850"
height="420"
/>
</div>
) : (
<div class="grid grid-cols-2 gap-4">
<Image
class="rounded-xl w-full"
src={getDirectusImageURL(imgOne)}
alt={imgOneAlt}
draggable="false"
format="webp"
loading="lazy"
width="400"
height="230"
/>
<Image
class="rounded-xl w-full mt-4 lg:mt-10"
src={getDirectusImageURL(imgTwo)}
alt={imgTwoAlt}
draggable="false"
format="webp"
loading="lazy"
width="400"
height="230"
/>
</div>
)}
</a>
</div>

View File

@@ -7,10 +7,12 @@ interface Props {
} }
const { dayName, label, icon, temp } = Astro.props; const { dayName, label, icon, temp } = Astro.props;
const sizeClasses = 'w-32 md:w-40';
--- ---
<div class="smooth-reveal-2 group flex flex-col"> <div class="smooth-reveal-2 group flex flex-col">
<div class="card-base w-32 md:w-40"> <div class={`card-base ${sizeClasses}`}>
<div class="p-5 text-center"> <div class="p-5 text-center">
<span class="card-text-description block font-bold text-xs uppercase tracking-widest"> <span class="card-text-description block font-bold text-xs uppercase tracking-widest">
{dayName} {dayName}

View File

@@ -3,8 +3,8 @@ import { readItems } from '@directus/sdk';
import type { Application } from '@lib/directusTypes'; import type { Application } from '@lib/directusTypes';
import HighlightsCard from '@components/cards/HighlightsCard.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import HighlightsCard from '@components/cards/HighlightsCard.astro';
const applications = ((await directus.request( const applications = ((await directus.request(
readItems('site_applications' as any, { readItems('site_applications' as any, {

View File

@@ -1,93 +0,0 @@
---
import { getCollection } from 'astro:content';
import { readItems } from '@directus/sdk';
import type { Post } from '@lib/directusTypes';
import CategoryCard from '@components/cards/CategoryCard.astro';
import directus from '@lib/directus';
import { timeago } from '@support/time';
const posts = await directus.request(
readItems('posts', {
filter: { published: { _eq: true } },
fields: ['*'],
sort: ['-published_date'],
})
);
const layoutPattern = [
{ col: 2, row: 2 },
{ col: 2, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 2 },
{ col: 2, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
];
const postMap: Map<string, Post[]> = posts
.sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf())
.reduce((acc, obj) => {
let posts = acc.get(obj.category);
if (!posts) {
posts = [];
}
posts.push(obj);
acc.set(obj.category, posts);
return acc;
}, new Map<string, Post[]>());
const categories = (await getCollection('categories'))
.sort((a, b) => {
const aCount = postMap.get(a.slug)?.length ?? 0;
const bCount = postMap.get(b.slug)?.length ?? 0;
return bCount - aCount;
})
.map((c, index) => {
const posts = postMap.get(c.slug);
const pattern = layoutPattern[index % layoutPattern.length];
const smColSpan = Math.min(pattern.col, 2);
const mdColSpan = Math.min(pattern.col, 4);
const rowSpan = pattern.row;
const rowSpanClass = rowSpan > 1 ? `row-span-${rowSpan}` : 'row-span-1';
const gridItemClass = `col-span-${smColSpan} md:col-span-${mdColSpan} ${rowSpanClass}`;
return {
...c,
posts,
gridItemClass,
layoutPattern: {
smCol: smColSpan,
mdCol: mdColSpan,
row: rowSpan,
index,
},
};
});
---
<section class="mx-auto px-4 py-10 sm:px-6 lg:px-8 lg:py-14 lg:pt-10 2xl:max-w-full">
<div class="grid grid-flow-row-dense grid-cols-2 md:grid-cols-4 gap-4">
{categories.map((category) => {
return (
<div
class={category.gridItemClass}
style={category.layoutPattern.row > 1 ? 'grid-row: span 2 / span 2;' : ''}
>
<CategoryCard
slug={category.slug}
title={category.data.title}
description={category.data.description}
count={postMap.get(category.slug)?.length ?? 0}
publishDate={timeago(postMap.get(category.slug)?.[0]?.published_date)}
/>
</div>
);
})}
</div>
</section>

View File

@@ -3,8 +3,9 @@ import { readItems } from '@directus/sdk';
import type { Education, Certificate} from '@lib/directusTypes'; import type { Education, Certificate} from '@lib/directusTypes';
import EducationCard from '@components/cards/EducationCard.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import { getDirectusImageURL } from '@lib/directusFunctions';
import EducationCard from '@components/cards/EducationCard.astro';
const educations = ((await directus.request( const educations = ((await directus.request(
readItems('site_education' as any, { readItems('site_education' as any, {
@@ -36,8 +37,8 @@ const certificates = ((await directus.request(
area={education.area} area={education.area}
date={education.graduationDate} date={education.graduationDate}
url={education.url} url={education.url}
logoUrlLight={education.logo} logoUrlLight={getDirectusImageURL(education.logo)}
logoUrlDark={education.logoDark} logoUrlDark={getDirectusImageURL(education.logoDark)}
/> />
))} ))}
</div> </div>

View File

@@ -78,7 +78,7 @@ const experiences = ((await directus.request(
</div> </div>
)} )}
{(experience.responsibilities || experience.achievements) && ( {(experience.responsibilities || experience.achievements) && (
<div class="relative flex flex-col gap-4 max-sm:h-auto! md:after:absolute md:after:bottom-0 md:after:h-12 md:after:w-full md:after:bg-linear-to-t md:after:from-neutral-200 dark:md:after:from-stone-700 md:after:content-[''] " :class="expanded ? 'after:hidden' : ''" x-show="expanded" x-collapse.min.50px> <div class="relative flex flex-col gap-4 max-sm:h-auto! md:after:absolute md:after:bottom-0 md:after:h-12 md:after:w-full md:after:bg-gradient-to-t md:after:from-neutral-200 dark:md:after:from-stone-700 md:after:content-[''] " :class="expanded ? 'after:hidden' : ''" x-show="expanded" x-collapse.min.50px>
{experience.responsibilities && ( {experience.responsibilities && (
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h4 class="text-header font-semibold"> <h4 class="text-header font-semibold">

View File

@@ -1,8 +1,8 @@
--- ---
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import FeaturesCard from '@components/cards/FeaturesCard.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import FeaturesCard from '@components/cards/FeaturesCard.astro';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
--- ---

View File

@@ -0,0 +1,36 @@
---
import { readItems } from '@directus/sdk';
import type { Post } from '@lib/directusTypes';
import directus from '@lib/directus';
import BlogCard from '@components/blog/BlogCard.astro';
const posts = await directus.request(
readItems('posts', {
filter: { published: { _eq: true } },
fields: ['*'],
sort: ['-published_date'],
})
);
const recentPosts = posts
.sort((a: Post, b: Post) => (new Date(b.published_date).getTime()) - (new Date(a.published_date).getTime()))
.slice(0, 3);
---
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
<h1 class="smooth-reveal card-text-header block">
Latest Posts
</h1>
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
<span class="card-text-header-description">
Checkout my most recent thoughts here
</span>
</div>
</div>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{recentPosts.map((b) => <BlogCard post={b} />)}
</div>
</section>

View File

@@ -3,8 +3,8 @@ import { readItems } from '@directus/sdk';
import type { Project } from '@lib/directusTypes'; import type { Project } from '@lib/directusTypes';
import HighlightsCard from '@components/cards/HighlightsCard.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import HighlightsCard from '@components/cards/HighlightsCard.astro';
const projects = ((await directus.request( const projects = ((await directus.request(
readItems('site_projects' as any, { readItems('site_projects' as any, {

View File

@@ -1,29 +0,0 @@
---
import type { Post } from '@lib/directusTypes';
import BlogCard from '@components/cards/BlogCard.astro';
interface Props {
posts: Post[];
title: string;
subTitle?: string;
}
const { posts, title, subTitle } = Astro.props;
---
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
<h1 class="smooth-reveal card-text-header block">
{title}
</h1>
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
<span class="card-text-header-description">
{subTitle}
</span>
</div>
</div>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{posts.map((b) => <BlogCard post={b} />)}
</div>
</section>

View File

@@ -1,35 +0,0 @@
---
import type { Post } from '@lib/directusTypes';
import LargeBlogLeftCard from '@components/cards/LargeBlogLeftCard.astro';
import LargeBlogRightCard from '@components/cards/LargeBlogRightCard.astro';
interface Props {
posts: Post[];
}
const { posts } = Astro.props;
---
<section class="smooth-reveal">
{posts.map((post, index) => index % 2 === 0 ? (
<LargeBlogLeftCard
title={post.title}
subTitle={post.description}
url={`/blog/${post.slug}`}
img={post.image}
imgAlt={post.image_alt}
/>
) : (
<LargeBlogRightCard
title={post.title}
subTitle={post.description}
url={`/blog/${post.slug}`}
single={!post.image_second}
imgOne={post.image}
imgOneAlt={post.image_alt}
imgTwo={post?.image_second}
imgTwoAlt={post?.image_second_alt}
/>
))}
</section>

View File

@@ -3,7 +3,6 @@ import WeatherCard from '@components/cards/WeatherCard.astro';
import { getFiveDayForecast } from '@support/weather'; import { getFiveDayForecast } from '@support/weather';
const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props; const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props;
const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, timezone); const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, timezone);
--- ---

View File

@@ -2,10 +2,10 @@
import { ClientRouter } from 'astro:transitions'; import { ClientRouter } from 'astro:transitions';
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus';
import BaseHead from '@components/BaseHead.astro'; import BaseHead from '@components/BaseHead.astro';
import Footer from '@components/Footer.astro'; import Footer from '@components/Footer.astro';
import Header from '@components/Header.astro'; import Header from '@components/Header.astro';
import directus from '@lib/directus';
import '@styles/global.css'; import '@styles/global.css';
@@ -20,16 +20,12 @@ interface Props {
const { title, description = 'Alex Lebens', ogImage, lang = 'en', structuredData } = Astro.props; const { title, description = 'Alex Lebens', ogImage, lang = 'en', structuredData } = Astro.props;
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
const normalizeTitle = !title ? global.name : `${title} | ${global.name}`; const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
--- ---
<html lang={lang}> <html lang={lang}>
<head> <head>
<title> <title>{normalizeTitle}</title>
{normalizeTitle}
</title>
<BaseHead <BaseHead
title={normalizeTitle} title={normalizeTitle}
description={description} description={description}
@@ -38,9 +34,7 @@ const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
ogDescription={description} ogDescription={description}
structuredData={structuredData} structuredData={structuredData}
/> />
<ClientRouter fallback="swap" /> <ClientRouter fallback="swap" />
<script is:inline> <script is:inline>
const theme = (() => { const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
@@ -59,35 +53,30 @@ const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
} }
window.localStorage.setItem('theme', theme); window.localStorage.setItem('theme', theme);
</script> </script>
<!-- Rybbit Tracking Snippet --> <!-- Rybbit Tracking Snippet -->
<script <script
src="https://rybbit.alexlebens.dev/api/script.js" src="https://rybbit.alexlebens.dev/api/script.js"
data-site-id={global.rybbit_site_id} data-site-id={global.rybbit_site_id}
defer defer
/> ></script>
</head> </head>
<body class="bg-stone-200 selection:bg-yellow-400 selection:text-neutral-700 dark:bg-stone-700">
<body class="bg-background selection:bg-yellow-400"> <div class="mx-auto w-full max-w-(--breakpoint-2xl) grow px-4 sm:px-6 lg:px-8">
<!-- Disabled texture background for now
<div class="fixed inset-0 -z-10">
<div class="bg-grid-pattern absolute inset-0 mask-[radial-gradient(white,transparent_85%)] bg-position-[center_top_-1px]"/>
</div>
-->
<div class="grow w-full max-w-(--breakpoint-2xl) px-4 sm:px-6 lg:px-8 py-20 mx-auto">
<Header /> <Header />
<main class="min-h-screen"> <main class="min-h-screen">
<slot /> <slot />
</main> </main>
</div> </div>
<Footer /> <Footer />
<style>
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
</body> </body>
</html> </html>

View File

@@ -28,7 +28,6 @@ const global = await directus.request(readSingleton('site_global'));
}, },
}} }}
> >
<section class="mt-20 grid place-content-center"> <section class="mt-20 grid place-content-center">
<div class="mx-auto max-w-7xl px-4 py-8 lg:px-6 lg:py-16"> <div class="mx-auto max-w-7xl px-4 py-8 lg:px-6 lg:py-16">
<div class="mx-auto max-w-screen-sm text-center"> <div class="mx-auto max-w-screen-sm text-center">
@@ -68,7 +67,6 @@ const global = await directus.request(readSingleton('site_global'));
</div> </div>
</div> </div>
</section> </section>
</BaseLayout> </BaseLayout>
<script> <script>

View File

@@ -1,20 +1,18 @@
--- ---
import { type CollectionEntry, getCollection } from 'astro:content'; import { type CollectionEntry, getCollection } from 'astro:content';
import getReadingTime from 'reading-time'; import getReadingTime from 'reading-time';
import { readItems, readSingleton } from '@directus/sdk';
import directus from '@lib/directus';
import { marked } from 'marked'; import { marked } from 'marked';
import markedShiki from 'marked-shiki'; import markedShiki from 'marked-shiki';
import { createHighlighter } from 'shiki'; import { createHighlighter } from 'shiki';
import { readItems, readSingleton } from '@directus/sdk'; import { getDirectusImageURL } from '@lib/directusFunctions';
import BaseLayout from '@layouts/BaseLayout.astro';
import Image from '@components/ui/images/Image.astro'; import Image from '@components/ui/images/Image.astro';
import SocialShareButton from '@components/buttons/SocialShareButton.astro'; import SocialShareButton from '@components/buttons/SocialShareButton.astro';
import BaseLayout from '@layouts/BaseLayout.astro';
import directus from '@lib/directus';
import { getDirectusImageURL } from '@lib/directusFunctions';
import { formatDateTime } from '@support/time'; import { formatDateTime } from '@support/time';
const post = Astro.props;
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await directus.request(readItems('posts')); const posts = await directus.request(readItems('posts'));
return posts.map((post) => ({ return posts.map((post) => ({
@@ -22,19 +20,18 @@ export async function getStaticPaths() {
props: post, props: post,
})); }));
} }
const post = Astro.props;
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
const category: CollectionEntry<'categories'> = (await getCollection('categories')) const category: CollectionEntry<'categories'> = (await getCollection('categories'))
.filter((c) => c.slug === post.category) .filter((c) => c.slug === post.category)
.pop() as CollectionEntry<'categories'>; .pop() as CollectionEntry<'categories'>;
const readingTime = getReadingTime(post.content); const readingTime = getReadingTime(post.content);
const highlighter = await createHighlighter({ const highlighter = await createHighlighter({
themes: ['github-light', 'github-dark', 'monokai'], themes: ['github-light', 'github-dark', 'monokai'],
langs: ['typescript', 'python', 'css', 'html', 'yaml', 'bash', 'json'], langs: ['typescript', 'python', 'css', 'html', 'yaml', 'bash', 'json'],
}); });
marked.use(markedShiki({ marked.use(markedShiki({
highlight(code, lang) { highlight(code, lang) {
return highlighter.codeToHtml(code, { return highlighter.codeToHtml(code, {
@@ -47,7 +44,6 @@ marked.use(markedShiki({
}); });
} }
})); }));
const content = marked.parse(post.content); const content = marked.parse(post.content);
--- ---
@@ -83,7 +79,6 @@ const content = marked.parse(post.content);
], ],
}} }}
> >
<section class="mx-auto max-w-6xl px-4 pt-8 pb-12 sm:px-6 lg:px-8 lg:pt-12"> <section class="mx-auto max-w-6xl px-4 pt-8 pb-12 sm:px-6 lg:px-8 lg:pt-12">
<div class="smooth-reveal relative w-full"> <div class="smooth-reveal relative w-full">
<div class="mt-4 rounded-2xl shadow-none sm:mt-0 sm:shadow-sm"> <div class="mt-4 rounded-2xl shadow-none sm:mt-0 sm:shadow-sm">
@@ -176,7 +171,6 @@ const content = marked.parse(post.content);
</div> </div>
</div> </div>
</section> </section>
<style is:inline> <style is:inline>
code[data-theme*=' '], code[data-theme*=' '],
code[data-theme*=' '] span { code[data-theme*=' '] span {
@@ -190,7 +184,6 @@ const content = marked.parse(post.content);
} }
} }
</style> </style>
</BaseLayout> </BaseLayout>
<script> <script>

View File

@@ -3,12 +3,11 @@ import { readItems, readSingleton } from '@directus/sdk';
import type { Post } from '@lib/directusTypes'; import type { Post } from '@lib/directusTypes';
import HeroSection from '@components/sections/HeroSection.astro';
import SelectedPostsSection from '@components/sections/SelectedPostsSection.astro';
import RecentPostsSection from '@components/sections/RecentPostsSection.astro';
import BaseLayout from '@layouts/BaseLayout.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro';
import BlogSelectedArticles from '@components/blog/BlogSelectedArticles.astro';
import BlogRecentArticles from '@components/blog/BlogRecentArticles.astro';
import HeroSection from '@components/sections/HeroSection.astro';
import blogImg from '@images/autumn_tree.png'; import blogImg from '@images/autumn_tree.png';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
@@ -19,11 +18,10 @@ const posts = await directus.request(
sort: ['-published_date'], sort: ['-published_date'],
}) })
); );
const selectedPosts: Post[] = posts.filter((p) => p.selected).slice(0, 4);
const selectedPosts: Post[] = posts.filter((p) => p.selected).slice(0, 3);
const recentPosts: Post[] = posts.filter( const recentPosts: Post[] = posts.filter(
(p) => !selectedPosts.some((selected) => selected.slug === p.slug) (p) => !selectedPosts.some((selected) => selected.slug === p.slug)
).slice(0, 9); ).slice(0, 6);
--- ---
<BaseLayout <BaseLayout
@@ -45,21 +43,10 @@ const recentPosts: Post[] = posts.filter(
}, },
}} }}
> >
<HeroSection title="Blog" subTitle={global.about_blog} src={blogImg} alt={global.blog_image_alt} />
<HeroSection <BlogSelectedArticles posts={selectedPosts} />
title="Blog" <BlogRecentArticles posts={recentPosts} />
subTitle={global.about_blog}
src={blogImg}
alt={global.blog_image_alt}
/>
<SelectedPostsSection posts={selectedPosts} />
<RecentPostsSection
posts={recentPosts}
title="Recent Posts"
/>
</BaseLayout> </BaseLayout>
<script> <script>

View File

@@ -2,12 +2,11 @@
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import { readItems, readSingleton } from '@directus/sdk'; import { readItems, readSingleton } from '@directus/sdk';
import type { Post } from '@lib/directusTypes';
import BlogCard from '@components/cards/BlogCard.astro';
import HeaderSection from '@components/sections/HeaderSection.astro';
import BaseLayout from '@layouts/BaseLayout.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import type { Post } from '@lib/directusTypes';
import BaseLayout from '@layouts/BaseLayout.astro';
import BlogCard from '@components/blog/BlogCard.astro';
import HeaderSection from '@components/sections/HeaderSection.astro';
export async function getStaticPaths() { export async function getStaticPaths() {
const categories = await getCollection('categories'); const categories = await getCollection('categories');
@@ -27,7 +26,6 @@ const posts = await directus.request(
sort: ['-published_date'], sort: ['-published_date'],
}) })
); );
const categoriesPosts = posts const categoriesPosts = posts
.sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf()) .sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf())
.filter((b) => { .filter((b) => {
@@ -53,7 +51,6 @@ const categoriesPosts = posts
}, },
}} }}
> >
<HeaderSection <HeaderSection
title={category.data.title} title={category.data.title}
subTitle={category.data.description} subTitle={category.data.description}
@@ -62,12 +59,9 @@ const categoriesPosts = posts
btnURL="/categories" btnURL="/categories"
/> />
<section class="max-w-340 2xl:max-w-full mx-auto mt-10 mb-10 px-4 py-8 sm:px-6 lg:px-8"> <section class="mx-auto mt-10 mb-10 max-w-[85rem] px-4 py-8 sm:px-6 lg:px-8 2xl:max-w-full">
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{categoriesPosts.map((b) => {categoriesPosts.map((b) => <BlogCard post={b} />)}
<BlogCard post={b} />
)}
</div> </div>
</section> </section>
</BaseLayout> </BaseLayout>

View File

@@ -1,14 +1,78 @@
--- ---
import { readSingleton } from '@directus/sdk'; import { getCollection } from 'astro:content';
import { readItems, readSingleton } from '@directus/sdk';
import type { Post } from '@lib/directusTypes';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import BlogCategoryCard from '@components/blog/BlogCategoryCard.astro';
import HeroSection from '@components/sections/HeroSection.astro'; import HeroSection from '@components/sections/HeroSection.astro';
import CategorySection from '@components/sections/CategorySection.astro'; import { timeago } from '@support/time';
import categoryImg from '@images/autumn_bench.png'; import categoryImg from '@images/autumn_bench.png';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
const posts = await directus.request(
readItems('posts', {
filter: { published: { _eq: true } },
fields: ['*'],
sort: ['-published_date'],
})
);
const postMap: Map<string, Post[]> = posts
.sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf())
.reduce((acc, obj) => {
let posts = acc.get(obj.category);
if (!posts) {
posts = [];
}
posts.push(obj);
acc.set(obj.category, posts);
return acc;
}, new Map<string, Post[]>());
const layoutPattern = [
{ col: 2, row: 2 },
{ col: 2, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 2 },
{ col: 2, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
];
const categories = (await getCollection('categories'))
.sort((a, b) => {
const aCount = postMap.get(a.slug)?.length ?? 0;
const bCount = postMap.get(b.slug)?.length ?? 0;
return bCount - aCount;
})
.map((c, index) => {
const posts = postMap.get(c.slug);
const pattern = layoutPattern[index % layoutPattern.length];
const smColSpan = Math.min(pattern.col, 2);
const mdColSpan = Math.min(pattern.col, 4);
const rowSpan = pattern.row;
const rowSpanClass = rowSpan > 1 ? `row-span-${rowSpan}` : 'row-span-1';
const gridItemClass = `col-span-${smColSpan} md:col-span-${mdColSpan} ${rowSpanClass} smooth-reveal-cards rounded-xl transition-all duration-300 shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg border border-stone-200/50 dark:border-stone-700/50`;
return {
...c,
posts,
gridItemClass,
layoutPattern: {
smCol: smColSpan,
mdCol: mdColSpan,
row: rowSpan,
index,
},
};
});
--- ---
<BaseLayout <BaseLayout
@@ -30,7 +94,6 @@ const global = await directus.request(readSingleton('site_global'));
}, },
}} }}
> >
<HeroSection <HeroSection
title="Categories" title="Categories"
subTitle={global.about_categories} subTitle={global.about_categories}
@@ -38,8 +101,28 @@ const global = await directus.request(readSingleton('site_global'));
alt={global.categories_image_alt} alt={global.categories_image_alt}
/> />
<CategorySection /> <section class="mx-auto px-4 py-10 sm:px-6 lg:px-8 lg:py-14 lg:pt-10 2xl:max-w-full">
<div class="grid grid-flow-row-dense grid-cols-2 gap-4 md:grid-cols-4">
{
categories.map((category) => {
return (
<div
class={category.gridItemClass}
style={category.layoutPattern.row > 1 ? 'grid-row: span 2 / span 2;' : ''}
>
<BlogCategoryCard
slug={category.slug}
title={category.data.title}
description={category.data.description}
count={postMap.get(category.slug)?.length ?? 0}
publishDate={timeago(postMap.get(category.slug)?.[0]?.published_date)}
/>
</div>
);
})
}
</div>
</section>
</BaseLayout> </BaseLayout>
<script> <script>

View File

@@ -1,31 +1,18 @@
--- ---
import { readSingleton, readItems } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import type { Post } from '@lib/directusTypes';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import HeroSection from '@components/sections/HeroSection.astro'; import HeroSection from '@components/sections/HeroSection.astro';
import FeatureSection from '@components/sections/FeatureSection.astro'; import FeaturesSection from '@components/sections/FeaturesSection.astro';
import WeatherSection from '@components/sections/WeatherSection.astro'; import WeatherSection from '@components/sections/WeatherSection.astro';
import RecentPostsSection from '@components/sections/RecentPostsSection.astro'; import LatestPostsSection from '@components/sections/LatestPostsSection.astro';
import GiteaSection from '@components/sections/GiteaSection.astro'; import GiteaSection from '@components/sections/GiteaSection.astro';
import homeImg from '@images/autumn_mountain.png'; import homeImg from '@images/autumn_mountain.png';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
const weather = await directus.request(readSingleton('site_weather')); const weather = await directus.request(readSingleton('site_weather'));
const posts = await directus.request(
readItems('posts', {
filter: { published: { _eq: true } },
fields: ['*'],
sort: ['-published_date'],
})
);
const recentPosts = posts
.sort((a: Post, b: Post) => (new Date(b.published_date).getTime()) - (new Date(a.published_date).getTime()))
.slice(0, 3);
--- ---
<BaseLayout <BaseLayout
@@ -47,7 +34,6 @@ const recentPosts = posts
}, },
}} }}
> >
<HeroSection <HeroSection
title={`Hello, I'm <span class="text-steel dark:text-steel">Alex Lebens</span>`} title={`Hello, I'm <span class="text-steel dark:text-steel">Alex Lebens</span>`}
subTitle={global.about_description} subTitle={global.about_description}
@@ -57,7 +43,7 @@ const recentPosts = posts
alt={global.home_image_alt} alt={global.home_image_alt}
/> />
<FeatureSection /> <FeaturesSection />
<WeatherSection <WeatherSection
server:defer server:defer
@@ -65,20 +51,16 @@ const recentPosts = posts
longitude={weather.longitude} longitude={weather.longitude}
cityName={weather.location} cityName={weather.location}
timezone={weather.timezone} timezone={weather.timezone}
/> >
</WeatherSection>
<RecentPostsSection <LatestPostsSection />
posts={recentPosts}
title="Latest Posts"
subTitle="Checkout my most recent thoughts here"
/>
<GiteaSection <GiteaSection
title="Follow me on Gitea" title="Follow me on Gitea"
subTitle="I love open source and have my code availabile on my Gitea server." subTitle="I love open source and have my code availabile on my Gitea server."
url="https://gitea.alexlebens.dev" url="https://gitea.alexlebens.dev"
/> />
</BaseLayout> </BaseLayout>
<script> <script>

View File

@@ -28,16 +28,11 @@
--color-main: light-dark(var(--color-steel), var(--color-bermuda)); --color-main: light-dark(var(--color-steel), var(--color-bermuda));
--color-accent: light-dark(var(--color-bronze), var(--color-desert)); --color-accent: light-dark(var(--color-bronze), var(--color-desert));
--color-active: light-dark(var(--color-orange-500), var(--color-orange-300));
--color-header: light-dark(var(--color-neutral-800), var(--color-neutral-200)); --color-header: light-dark(var(--color-neutral-800), var(--color-neutral-200));
--color-primary: light-dark(var(--color-neutral-600), var(--color-neutral-200)); --color-primary: light-dark(var(--color-neutral-600), var(--color-neutral-200));
--color-primary-hover: light-dark(var(--color-neutral-800), var(--color-neutral-400)); --color-primary-hover: light-dark(var(--color-neutral-800), var(--color-neutral-400));
--color-secondary: light-dark(var(--color-neutral-500), var(--color-neutral-400)); --color-secondary: light-dark(var(--color-neutral-500), var(--color-neutral-400));
--color-secondary-hover: light-dark(var(--color-neutral-800), var(--color-neutral-200));
--color-background: light-dark(var(--color-neutral-200), var(--color-stone-700));
--color-background-accent: light-dark(color-mix(in srgb, var(--color-stone-300) 40%, transparent), color-mix(in srgb, var(--color-stone-800) 20%, transparent));
} }
@layer base { @layer base {

View File

@@ -37,7 +37,7 @@
@utility button-bg-teal { @utility button-bg-teal {
@apply transition-all duration-300 @apply transition-all duration-300
bg-bermuda hover:bg-turquoise group-hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda dark:group-hover:bg-bermuda bg-bermuda hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda
} }
@utility button-bg-neutral { @utility button-bg-neutral {
@@ -53,20 +53,12 @@
/* Card classes */ /* Card classes */
@utility card-base { @utility card-base {
@apply transition-all duration-300 @apply rounded-xl
rounded-xl
border border-neutral-100 dark:border-stone-500/20 border border-neutral-100 dark:border-stone-500/20
bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90 bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90
shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg
} }
@utility card-base-hidden {
@apply transition-all duration-300
rounded-xl
border border-transparent
hover:bg-neutral-400/20 dark:hover:bg-neutral-800/40
}
@utility card-hover-icon-color { @utility card-hover-icon-color {
@apply transition-all duration-300 @apply transition-all duration-300
text-primary text-primary
@@ -87,8 +79,9 @@
@utility card-text-header-minor { @utility card-text-header-minor {
@apply text-header @apply text-header
text-2xl md:text-3xl md:text-3xl
font-semibold leading-tight tracking-tight text-balance text-2xl
font-semibold
} }
@utility card-text-header-description { @utility card-text-header-description {
@@ -102,22 +95,11 @@
font-bold font-bold
} }
@utility card-text-title-major {
@apply text-header
text-4xl md:text-3xl
font-bold leading-tight tracking-tight text-balance
}
@utility card-hover-text-title { @utility card-hover-text-title {
@apply transition-all duration-300 @apply transition-all duration-300
group-hover:text-main group-hover:text-main
} }
@utility card-hover-text-neutral {
@apply transition-all duration-300
group-hover:text-primary-hover
}
@utility card-hover-text-gitea { @utility card-hover-text-gitea {
@apply transition-all duration-300 @apply transition-all duration-300
group-hover:text-gitea-primary group-hover:text-gitea-primary
@@ -126,10 +108,3 @@
@utility card-text-description { @utility card-text-description {
@apply text-secondary @apply text-secondary
} }
/* Misc */
@utility nav-base {
@apply border border-neutral-100 dark:border-stone-500/20
bg-neutral-100 dark:bg-neutral-800
shadow-xs dark:shadow-md
}