Compare commits

..

3 commits

Author SHA1 Message Date
fb42c45897 Merge pull request 'ui-redesign' (#1) from ui-redesign into main
Reviewed-on: #1
2026-09-20 03:31:00 +00:00
21cb00d377 theme toggle error fixed 2026-09-20 00:29:38 -03:00
ce5912caaf partial font swap 2026-09-19 11:13:42 -03:00
12 changed files with 132 additions and 151 deletions

2
mise.toml Normal file
View file

@ -0,0 +1,2 @@
[tools]
claude = "latest"

View file

@ -10,7 +10,7 @@ const t = useTranslations(lang);
const year = new Date().getFullYear();
---
<footer class="border-t border-border/80">
<footer class="border-t border-border/80 font-mono">
<div class="mx-auto flex max-w-3xl flex-col gap-2 px-4 py-8 text-sm text-text-muted sm:flex-row sm:items-center sm:justify-between sm:px-6">
<p>
&copy; {year} <span class="font-mono text-text">gabeefranco</span> &middot; {t('footer.rights')}

View file

@ -28,24 +28,71 @@ function isActive(href: string): boolean {
}
---
<header class="sticky top-0 z-40 border-b border-border/80 bg-bg/85 backdrop-blur">
<div class="mx-auto flex max-w-3xl items-center justify-between gap-4 px-4 py-3 sm:px-6">
<a href={homeHref} class="flex shrink-0 items-center gap-2.5" aria-label={t('site.name')}>
<img src="/favicon.svg" alt="" width="32" height="32" class="size-8" />
<span class="font-mono text-[15px] font-semibold tracking-tight text-text">{t('site.name')}</span>
</a>
<div class="flex items-center gap-2">
<nav class="hidden items-center gap-1 sm:flex" aria-label={t('nav.menu')}>
<header class="sticky top-0 z-40 border-b border-border/80 bg-bg/85 backdrop-blur">
<div class="flex flex-col justify-center gap-4 px-4 py-3 sm:px-6">
<div class="flex flex-row justify-between items-end">
<a href={homeHref} class="flex hover:underline shrink-0 items-center gap-2.5" aria-label={t('site.name')}>
<img src="/logo.png" class="h-4"/>
<span class="font-mono text-[15px] font-semibold tracking-tight text-text">{t('site.name')}</span>
</a>
<div class="ml-10 flex sm:hidden justify-end gap-3">
<a
href={langSwitchHref}
hreflang={otherLang}
title={t('lang.switch')}
aria-label={`${t('lang.switch')}: ${LANG_LABELS[otherLang]}`}
class="text-accent max-w-fit font-mono text-xs sm:text-sm underline hover:cursor-pointer hover:text-accent-deep"
>
{`${t('lang.switch')}: ${LANG_LABELS[otherLang]}`}
</a>
<ThemeToggle lang={lang} />
</div>
<div class="hidden sm:flex items-center gap-2">
<nav class="flex items-center gap-1 justify-right" aria-label={t('nav.menu')}>
{navLinks.map((link) => (
<a
href={link.href}
aria-current={isActive(link.href) ? 'page' : undefined}
class:list={[
'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',
'px-2 py-1 text-sm font-medium font-mono underline',
isActive(link.href)
? 'bg-primary/10 text-primary'
: 'text-text-muted hover:bg-surface-alt hover:text-text',
? 'text-primary hover:text-primary-hover'
: 'text-text-muted hover:text-text',
]}
>
{link.label}
</a>
))}
</nav>
<a
href={langSwitchHref}
hreflang={otherLang}
title={t('lang.switch')}
aria-label={`${t('lang.switch')}: ${LANG_LABELS[otherLang]}`}
class="text-accent max-w-fit py-1 font-mono text-xs sm:text-sm underline hover:cursor-pointer hover:text-accent-deep "
>
{`${t('lang.switch')}: ${LANG_LABELS[otherLang]}`}
</a>
<ThemeToggle lang={lang} />
</div>
</div>
<div class="flex justify-end sm:hidden items-center gap-2">
<nav class="flex items-center gap-1 justify-right" aria-label={t('nav.menu')}>
{navLinks.map((link) => (
<a
href={link.href}
aria-current={isActive(link.href) ? 'page' : undefined}
class:list={[
'px-2 py-1 text-sm font-medium font-mono underline',
isActive(link.href)
? 'text-primary hover:text-primary-hover'
: 'text-text-muted hover:text-text',
]}
>
{link.label}
@ -53,47 +100,7 @@ function isActive(href: string): boolean {
))}
</nav>
<a
href={langSwitchHref}
hreflang={otherLang}
title={t('lang.switch')}
aria-label={`${t('lang.switch')}: ${LANG_LABELS[otherLang]}`}
class="grid size-9 shrink-0 place-items-center rounded-full border border-border text-xs font-bold uppercase text-text-muted transition-colors hover:border-primary hover:text-primary"
>
{otherLang}
</a>
<ThemeToggle lang={lang} />
<details class="relative sm:hidden">
<summary
class="grid size-9 cursor-pointer list-none place-items-center rounded-full border border-border text-text-muted transition-colors hover:border-primary hover:text-primary"
aria-label={t('nav.menu')}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="size-[18px]">
<path d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</summary>
<nav
class="absolute right-0 top-11 flex w-40 flex-col gap-1 rounded-xl border border-border bg-surface p-2 shadow-lg"
aria-label={t('nav.menu')}
>
{navLinks.map((link) => (
<a
href={link.href}
aria-current={isActive(link.href) ? 'page' : undefined}
class:list={[
'rounded-lg px-3 py-2 text-sm font-medium transition-colors',
isActive(link.href)
? 'bg-primary/10 text-primary'
: 'text-text-muted hover:bg-surface-alt hover:text-text',
]}
>
{link.label}
</a>
))}
</nav>
</details>
</div>
</div>
</header>

View file

@ -15,46 +15,53 @@ const postsHref = getLocalizedPath('/posts', lang);
const aboutHref = getLocalizedPath('/about', lang);
---
<section class="mx-auto max-w-3xl px-4 pb-4 pt-14 sm:px-6 sm:pt-20">
<div class="flex flex-col items-start gap-8 sm:flex-row sm:items-center">
<img
src="/logo.png"
alt=""
width="867"
height="458"
class="w-40 shrink-0 drop-shadow-sm sm:w-52"
/>
<div>
<p class="text-sm font-semibold uppercase tracking-wide text-primary">{t('hero.kicker')}</p>
<h1 class="mt-2 text-3xl font-bold tracking-tight text-text sm:text-4xl">{t('hero.title')}</h1>
<p class="mt-4 max-w-xl text-lg leading-relaxed text-text-muted">{t('hero.subtitle')}</p>
<div class="mt-6 flex flex-wrap gap-3">
<a
href={postsHref}
class="rounded-full bg-primary px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-primary-hover"
>
{t('hero.cta.posts')}
</a>
<a
href={aboutHref}
class="rounded-full border border-border px-5 py-2.5 text-sm font-semibold text-text transition-colors hover:border-primary hover:text-primary"
>
{t('hero.cta.about')}
</a>
</div>
</div>
<section class="mx-10 my-10 flex flex-col gap-5 font-mono">
<div class="flex flex-col gap-2">
<h1 class="text-text text-2xl">{t('hero.title')}</h1>
<h2 class="text-text text-lg">{t('hero.subtitle')}</h2>
</div>
</section>
<section class="mx-auto max-w-3xl px-4 py-12 sm:px-6">
<!-- <section class="mx-auto max-w-3xl px-4 pb-4 pt-14 sm:px-6 sm:pt-20 font-mono"> -->
<!-- <div class="flex flex-col items-start gap-8 sm:flex-row sm:items-center"> -->
<!-- <img -->
<!-- src="/logo.png" -->
<!-- alt="" -->
<!-- width="867" -->
<!-- height="458" -->
<!-- class="w-40 shrink-0 drop-shadow-sm sm:w-52" -->
<!-- /> -->
<!-- <div> -->
<!-- <p class="text-sm font-semibold uppercase tracking-wide text-primary">{t('hero.kicker')}</p> -->
<!-- <h1 class="mt-2 text-3xl font-bold tracking-tight text-text sm:text-4xl">{t('hero.title')}</h1> -->
<!-- <p class="mt-4 max-w-xl text-lg leading-relaxed text-text-muted">{t('hero.subtitle')}</p> -->
<!-- <div class="mt-6 flex flex-wrap gap-5"> -->
<!-- <a -->
<!-- href={postsHref} -->
<!-- class="text-primary hover:underline font-bold px-2 py-1 bg-primary/10" -->
<!-- > -->
<!-- {t('hero.cta.posts')} -->
<!-- </a> -->
<!-- <a -->
<!-- href={aboutHref} -->
<!-- class="text-text hover:underline font-bold px-2 py-1 bg-text/5" -->
<!-- > -->
<!-- {t('hero.cta.about')} -->
<!-- </a> -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<!-- </section> -->
<section class="mx-10 font-mono">
<div class="flex items-baseline justify-between gap-4">
<div>
<h2 class="text-xl font-semibold text-text">{t('home.recent')}</h2>
<p class="mt-1 text-sm text-text-muted">{t('home.recent.subtitle')}</p>
<p class="mt-1 text-sm text-text-muted mb-2">{t('home.recent.subtitle')}</p>
<a href={postsHref} class="shrink-0 text-sm font-medium text-primary hover:underline">
{t('home.viewAll')} &rarr;
</a>
</div>
<a href={postsHref} class="shrink-0 text-sm font-medium text-primary hover:underline">
{t('home.viewAll')} &rarr;
</a>
</div>
{recentPosts.length > 0 ? (

View file

@ -13,12 +13,12 @@ const href = getLocalizedPath(`/posts/${slugOf(post)}`, lang);
const tagsHref = (tag: string) => getLocalizedPath(`/tags/${tag}`, lang);
---
<article class="group border-b border-border py-6 first:pt-0 last:border-b-0">
<article class="group border-b border-border py-6 first:pt-0 last:border-b-0 font-mono">
<a href={href} class="block">
<time datetime={post.data.pubDate.toISOString()} class="text-xs font-medium uppercase tracking-wide text-text-muted">
{formatDate(post.data.pubDate, lang)}
</time>
<h3 class="mt-1.5 text-xl font-semibold text-text transition-colors group-hover:text-primary">
<h3 class="mt-1.5 text-xl font-semibold text-text group-hover:text-primary group-hover:underline">
{post.data.title}
</h3>
<p class="mt-1.5 text-[15px] leading-relaxed text-text-muted">{post.data.description}</p>
@ -28,7 +28,7 @@ const tagsHref = (tag: string) => getLocalizedPath(`/tags/${tag}`, lang);
{post.data.tags.map((tag) => (
<a
href={tagsHref(tag)}
class="rounded-full bg-surface-alt px-2.5 py-1 text-xs font-medium text-text-muted transition-colors hover:bg-accent/15 hover:text-accent"
class="text-text-muted px-1 py-0.5 text-sm hover:text-accent underline"
>
#{tag}
</a>

View file

@ -15,19 +15,19 @@ const posts = tag ? allPosts.filter((p) => p.data.tags.includes(tag)) : allPosts
const postsHref = getLocalizedPath('/posts', lang);
---
<section class="mx-auto max-w-3xl px-4 py-12 sm:px-6">
<section class="mx-10 mt-10">
{tag ? (
<>
<p class="text-sm font-medium text-text-muted">{t('posts.filteredBy')}</p>
<h1 class="mt-1 text-3xl font-bold tracking-tight text-text">#{tag}</h1>
<a href={postsHref} class="mt-2 inline-block text-sm font-medium text-primary hover:underline">
<p class="text-sm font-mono font-medium text-text-muted">{t('posts.filteredBy')}</p>
<h1 class="mt-1 font-mono text-3xl font-bold tracking-tight text-text">#{tag}</h1>
<a href={postsHref} class="font-mono mt-2 inline-block text-sm font-medium text-primary hover:underline">
{t('posts.clearFilter')}
</a>
</>
) : (
<>
<h1 class="text-3xl font-bold tracking-tight text-text">{t('posts.title')}</h1>
<p class="mt-2 text-text-muted">{t('posts.subtitle')}</p>
<h1 class="font-mono text-3xl font-bold tracking-tight text-text">{t('posts.title')}</h1>
<p class="font-mono mt-2 text-text-muted">{t('posts.subtitle')}</p>
</>
)}

View file

@ -11,61 +11,26 @@ const t = useTranslations(lang);
---
<button
id="theme-toggle"
type="button"
class="theme-toggle text-accent sm:ml-2 font-mono text-xs sm:text-sm underline hover:cursor-pointer hover:text-accent-deep"
aria-label={t('theme.switch')}
title={t('theme.switch')}
class="grid size-9 shrink-0 place-items-center rounded-full border border-border text-text-muted transition-colors hover:border-primary hover:text-primary"
>
<svg
id="theme-toggle-sun"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="hidden size-[18px]"
>
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
></path>
</svg>
<svg
id="theme-toggle-moon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="hidden size-[18px]"
>
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5Z"></path>
</svg>
{t('theme.switch')}
</button>
<script is:inline>
(function () {
const btn = document.getElementById('theme-toggle');
const sun = document.getElementById('theme-toggle-sun');
const moon = document.getElementById('theme-toggle-moon');
if (!btn || !sun || !moon) return;
function sync() {
const isDark = document.documentElement.classList.contains('dark');
sun.classList.toggle('hidden', !isDark);
moon.classList.toggle('hidden', isDark);
<script>
function init() {
const buttons = document.querySelectorAll('.theme-toggle');
buttons.forEach((btn) => {
btn.addEventListener('click', function () {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
});
}
btn.addEventListener('click', function () {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
sync();
});
init();
sync();
})();
document.addEventListener('astro:after-swap', init);
</script>

View file

@ -15,8 +15,8 @@ const postsHref = getLocalizedPath('/posts', lang);
const tagsHref = (tag: string) => getLocalizedPath(`/tags/${tag}`, lang);
---
<article class="mx-auto max-w-3xl px-4 py-12 sm:px-6">
<a href={postsHref} class="inline-flex items-center gap-1.5 text-sm font-medium text-text-muted transition-colors hover:text-primary">
<article class="mx-10 mt-10 font-mono">
<a href={postsHref} class="underline inline-flex items-center gap-1.5 text-sm font-medium text-text-muted hover:text-primary">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4">
<path d="M15 18l-6-6 6-6"></path>
</svg>
@ -42,7 +42,7 @@ const tagsHref = (tag: string) => getLocalizedPath(`/tags/${tag}`, lang);
{post.data.tags.map((tag) => (
<a
href={tagsHref(tag)}
class="rounded-full bg-surface-alt px-2.5 py-1 text-xs font-medium text-text-muted transition-colors hover:bg-accent/15 hover:text-accent"
class=" text-xs font-medium text-text-muted underline hover:text-accent"
>
#{tag}
</a>

View file

@ -19,7 +19,7 @@ export const ui = {
'hero.kicker': "Gabriel Franco's notes",
'hero.title': 'Building things, and figuring out how they work.',
'hero.subtitle':
"Notes on computer science, systems, and the open source projects that shaped how I think about software. I'm gabeefranco — welcome.",
"Notes on computer science, systems, and the open source projects that shaped how I think about software. I'm Gabriel Franco — welcome.",
'hero.cta.posts': 'Read the posts',
'hero.cta.about': 'About me',
'home.recent': 'Recent posts',
@ -54,7 +54,7 @@ export const ui = {
'hero.kicker': 'Anotações do Gabriel Franco',
'hero.title': 'Construindo coisas e entendendo como elas funcionam.',
'hero.subtitle':
'Notas sobre ciência da computação, sistemas e os projetos open source que moldaram a forma como penso sobre software. Eu sou o gabeefranco — seja bem-vindo.',
'Notas sobre ciência da computação, sistemas e os projetos open source que moldaram a forma como penso sobre software. Eu sou o Gabriel Franco — seja bem-vindo.',
'hero.cta.posts': 'Ler os posts',
'hero.cta.about': 'Sobre mim',
'home.recent': 'Posts recentes',

View file

@ -10,7 +10,7 @@ const langs: Lang[] = ['en', 'pt'];
---
<BaseLayout title={`${ui.en['404.title']} — gabeefranco`} description={ui.en['404.body']} lang="en">
<section class="mx-auto flex max-w-3xl flex-col items-start px-4 py-24 sm:px-6">
<section class="mx-10 flex flex-col items-start px-4 py-24 sm:px-6 font-mono">
<p class="font-mono text-sm text-primary">404</p>
{langs.map((lang) => (
<div data-notfound-lang={lang} class={lang === 'en' ? '' : 'hidden'}>
@ -18,7 +18,7 @@ const langs: Lang[] = ['en', 'pt'];
<p class="mt-3 text-text-muted">{ui[lang]['404.body']}</p>
<a
href={getLocalizedPath('/', lang)}
class="mt-6 inline-block rounded-full bg-primary px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-primary-hover"
class="mt-4 inline-block underline text-primary hover:text-primary-hover font-bold"
>
{ui[lang]['404.cta']}
</a>

View file

@ -8,7 +8,7 @@ const description =
---
<BaseLayout title={`${t('about.title')} — ${t('site.name')}`} description={description} lang="en">
<section class="mx-auto max-w-3xl px-4 py-14 sm:px-6">
<section class="mx-10 mt-10 font-mono">
<h1 class="text-3xl font-bold tracking-tight text-text sm:text-4xl">{t('about.title')}</h1>
<div class="prose prose-neutral mt-6 max-w-none prose-headings:font-semibold prose-a:text-primary prose-a:no-underline hover:prose-a:underline dark:prose-invert">

View file

@ -8,7 +8,7 @@ const description =
---
<BaseLayout title={`${t('about.title')} — ${t('site.name')}`} description={description} lang="pt">
<section class="mx-auto max-w-3xl px-4 py-14 sm:px-6">
<section class="mx-10 mt-10 font-mono">
<h1 class="text-3xl font-bold tracking-tight text-text sm:text-4xl">{t('about.title')}</h1>
<div class="prose prose-neutral mt-6 max-w-none prose-headings:font-semibold prose-a:text-primary prose-a:no-underline hover:prose-a:underline dark:prose-invert">