Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Personal site at https://yuler.dev — Astro 6, MDX, Tailwind CSS 4, TypeScript.

## Icons

Every icons should a simple astro component locate in [`src/components/icons`](./src/components/)
Every icon should be a simple Astro component located in [`src/components/icons`](./src/components/icons/)

## Git Commit

Expand All @@ -28,3 +28,8 @@ Use the `/git-commit` skill for every commit. Check in this order:
- Global: `~/.agents/skills/git-commit`

If neither exists, install it from the upstream [skills/git-commit](https://github.com/yuler/skills/tree/main/skills/git-commit)

## Deployment

- This site deploys as static files generated from the Astro build.
- Deployment is automated by [`.github/workflows/deploy.yml`](./.github/workflows/deploy.yml): pushes to `main` run the GitHub Actions workflow, build the site with `withastro/action`, and publish it to GitHub Pages with `actions/deploy-pages`.
3 changes: 2 additions & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ colors:
surface: "#ffffff"
border: "#e5e7eb"
border-muted: "#f3f4f6"
muted-ui: "#9ca3af"
heatmap-low: "#f3f4f6"
heatmap-mid: "#d1d5db"
heatmap-high: "#4b5563"
Expand Down Expand Up @@ -119,7 +120,7 @@ yuler.dev is a **light, content-first** personal site: soft neutral canvas, whit
- **Neutral (`#f5f5f5`):** Page background for home, posts, and workouts (`bg-[#f5f5f5]` / same as body background).
- **Surface (`#ffffff`):** All primary cards and article shells.
- **Border (`#e5e7eb`) / border-muted (`#f3f4f6`):** Default card and list borders; lighter rules for section dividers (`border-gray-100`).
- **Muted UI (`#9ca3af`, Tailwind `gray-400`):** Chevron and separator icons; decorative only, not for long text on white (contrast). Not a named YAML token—use Tailwind classes in code.
- **Muted UI (`#9ca3af`, Tailwind `gray-400`):** Chevron icons, corner markers, decorative separators, and inactive controls. Use for short UI chrome only, not long text on white (contrast).
- **Heatmap scale (`heatmap-*`):** Workout contribution cells only—from empty light gray through black for intensity; today’s cell may use an inset ring, not a fifth fill color.

## Typography
Expand Down
11 changes: 8 additions & 3 deletions src/components/Footer.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
---
const today = new Date()
const showSiteLinks = Astro.url.pathname !== '/'
---

<footer class="py-8 text-center text-sm text-gray-500">
<p class="mb-2 font-mono text-xs">
This website follows the <a href="/design" class="underline! decoration-gray-400 underline-offset-4 hover:text-gray-900 hover:decoration-gray-900 transition-colors">design guidelines</a> and provides <a href="/agents" class="underline! decoration-gray-400 underline-offset-4 hover:text-gray-900 hover:decoration-gray-900 transition-colors">agent instructions</a>.
</p>
{
showSiteLinks && (
<p class="mb-2 font-mono text-xs">
This website follows the <a href="/design" class="underline! decoration-gray-400 underline-offset-4 hover:text-gray-900 hover:decoration-gray-900 transition-colors">design guidelines</a> and provides <a href="/agents" class="underline! decoration-gray-400 underline-offset-4 hover:text-gray-900 hover:decoration-gray-900 transition-colors">agent instructions</a>.
</p>
)
}
<p>&copy; {today.getFullYear()} Yu Le. All rights reserved.</p>
</footer>
5 changes: 5 additions & 0 deletions src/components/Signature.astro
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ const { class: className = '' } = Astro.props
? cssDuration * 1000
: DEFAULT_DURATION_MS

if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
signaturePath.style.strokeDashoffset = '0'
return
}

async function play(from: number, to: number, duration: number): Promise<void> {
const animation = signaturePath.animate(
[{ strokeDashoffset: String(from) }, { strokeDashoffset: String(to) }],
Expand Down
2 changes: 1 addition & 1 deletion src/components/Thought.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const TAG_LEADING_HASH = /^#/
const tags = thought.data.tags ?? []
const contentProseClass = [
'prose prose-sm prose-gray max-w-none text-sm leading-relaxed text-gray-900',
'[&_a:hover]:text-gray-900 [&_a]:text-gray-900 [&_a]:underline [&_a]:decoration-gray-400',
'[&_a:hover]:text-gray-900 [&_a]:text-gray-900 [&_a]:underline [&_a]:decoration-gray-400 [&_a]:underline!',
'[&_code]:rounded-sm [&_code]:bg-gray-100 [&_code]:px-1 [&_code]:py-px [&_code]:text-xs',
'[&_h2]:mb-2 [&_h2]:mt-4 [&_h2]:text-sm [&_h2]:font-semibold [&_h2]:text-gray-900',
'[&_h3]:text-xs [&_h3]:font-semibold [&_h3]:text-gray-800',
Expand Down
31 changes: 31 additions & 0 deletions src/components/cards/AgentStuff.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
import CornerMarkers from '../CornerMarkers.astro'
import Bot from '../icons/Bot.astro'
import Sparkles from '../icons/Sparkles.astro'
---

<div class="relative bg-white border border-gray-200 p-6 h-auto transition-all duration-300 hover:border-gray-400">
<CornerMarkers />

<div class="pointer-events-none absolute inset-0 overflow-hidden text-gray-900 opacity-[0.05]">
<Sparkles class="absolute left-2 top-2 size-16 -rotate-12" />
<Bot class="absolute right-2 bottom-2 size-14 rotate-12" />
</div>

<p class="relative z-10 font-mono text-xs leading-relaxed text-gray-500 break-all">
Comment thread
yuler marked this conversation as resolved.
This website follows the
<a
href="/design"
class="underline! decoration-gray-400 underline-offset-4 transition-colors hover:text-gray-900 hover:decoration-gray-900"
>
design guidelines
</a>
and provides
<a
href="/agents"
class="underline! decoration-gray-400 underline-offset-4 transition-colors hover:text-gray-900 hover:decoration-gray-900"
>
agent instructions
</a>.
</p>
</div>
12 changes: 6 additions & 6 deletions src/components/cards/Location.astro
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ const {
>
</div>
<div
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-white/95 via-white/80 to-white/60 z-[401]"
class="pointer-events-none absolute inset-0 bg-linear-to-t from-white/95 via-white/80 to-white/60 z-[401]"
>
</div>
</div>

<div class="absolute right-0 top-0 opacity-5 z-10">
<MapPin class="size-20 sm:size-28 lg:size-32" />
<div class="absolute right-2 bottom-2 opacity-5 z-10">
<MapPin class="size-12 " />
</div>

<div class="relative z-10 flex h-full flex-col p-4">
<div class="text-left mt-auto border-t border-gray-100 pt-3">
<div class="relative z-10 flex h-full flex-col p-4 pt-8">
<div class="text-left mt-auto border-t border-gray-100">
<h3 class="text-base font-semibold text-gray-900 font-mono">{title}</h3>
<p class="text-xs text-gray-500 font-mono">{subtitle}</p>
</div>
Expand All @@ -62,7 +62,7 @@ const {
</a>

<button
class="location-provider-btn absolute top-2 left-2 z-[402] flex items-center justify-center bg-white/80 backdrop-blur-sm p-1 rounded-sm border border-gray-200 text-gray-400 hover:text-gray-900 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 cursor-pointer"
class="location-provider-btn absolute top-2 right-2 z-402 flex items-center justify-center bg-white/80 backdrop-blur-sm p-1 rounded-sm border border-gray-200 text-gray-400 hover:text-gray-900 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 cursor-pointer"
aria-label="Switch map provider"
title="Switch map provider"
>
Expand Down
109 changes: 62 additions & 47 deletions src/components/cards/Posts.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import ArrowRight from '../icons/ArrowRight.astro'
import ChevronRight from '../icons/ChevronRight.astro'

interface Props {
class?: string
limit?: number
}

const { limit = 5 } = Astro.props
const { class: className, limit = 5 } = Astro.props

const posts = (
await getCollection('posts', ({ data }) => {
Expand All @@ -19,62 +20,76 @@ const posts = (
---

<div
class="relative bg-white border border-gray-200 p-6 h-full flex flex-col transition-all duration-300 hover:border-gray-400"
class={`relative flex h-auto flex-col border border-gray-200 bg-white p-6 transition-all duration-300 hover:border-gray-400 ${className ?? ''}`}
>
<CornerMarkers />

<div
class="mb-4 flex items-center justify-between border-b border-gray-100 pb-4"
>
<h2 class="text-lg font-semibold text-gray-900 font-mono">Posts</h2>
<span class="px-2 py-0.5 text-sm bg-gray-100 text-gray-600 font-mono">
{posts.length}
</span>
</div>

<ul class="flex-1 space-y-1">
{
posts.slice(0, limit).map(post => (
<li>
<a
href={`/posts/${post.id}`}
class="group flex items-center justify-between p-3 min-h-[3.25rem] transition-colors hover:bg-gray-50 border border-transparent hover:border-gray-200"
>
<div>
<h3 class="font-medium text-gray-900 group-hover:text-gray-700">
{post.data.title}
{post.data.draft && (
<span class="ml-2 px-1.5 py-0.5 text-xs bg-gray-200 text-gray-600">
Draft
</span>
)}
{post.data.wip && (
<span class="ml-2 px-1.5 py-0.5 text-xs bg-amber-100 text-amber-700">
WIP
</span>
)}
</h3>
<time
class="text-xs text-gray-500 font-mono"
datetime={post.data.date.toISOString()}
>
{formatDate(post.data.date)}
</time>
</div>
<ChevronRight class="h-4 w-4 text-gray-400 transition-transform group-hover:translate-x-0.5" />
</a>
</li>
))
}
</ul>

<div class="mt-auto flex justify-center pt-6 border-t border-gray-100">
<h2 class="text-lg font-semibold text-gray-900 font-mono">Posts
<span class="px-2 py-0.5 text-sm bg-gray-100 text-gray-600 font-mono">
{posts.length}
</span>
</h2>
<a
href="/posts"
class="inline-flex items-center gap-2 px-4 py-3 min-h-[2.75rem] text-sm bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-900 transition-colors font-mono"
class="inline-flex items-center gap-1 text-xs text-gray-400 font-mono transition-colors hover:text-gray-600 underline underline-offset-2 decoration-gray-200 hover:decoration-gray-400"
>
View more
View all
<ArrowRight class="h-3 w-3" />
</a>
</div>

<div class="relative min-h-0 flex-1 overflow-hidden">
<ul class="space-y-1 pb-8">
{
posts.slice(0, limit).map(post => (
<li>
<a
href={`/posts/${post.id}`}
class="group flex min-h-13 items-start justify-between gap-3 p-3 transition-colors hover:bg-gray-50 border border-transparent hover:border-gray-200"
>
<div class="min-w-0">
<h3 class="font-medium text-gray-900 group-hover:text-gray-700">
{post.data.title}
{post.data.draft && (
<span class="ml-2 px-1.5 py-0.5 text-xs bg-gray-200 text-gray-600">
Draft
</span>
)}
{post.data.wip && (
<span class="ml-2 px-1.5 py-0.5 text-xs bg-amber-100 text-amber-700">
WIP
</span>
)}
</h3>
<p class="mt-1 line-clamp-2 text-sm leading-snug text-gray-600">
{post.data.description}
</p>
<time
class="mt-2 block text-xs text-gray-500 font-mono"
datetime={post.data.date.toISOString()}
>
{formatDate(post.data.date)}
</time>
{post.data.tags && post.data.tags.length > 0 && (
<div class="mt-2 flex flex-wrap gap-1.5">
{post.data.tags.slice(0, 3).map(tag => (
<span class="border border-gray-200 px-1.5 py-0.5 text-[10px] leading-none text-gray-500">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The font size 10px is below the smallest defined typography token in the design guide (mono-label at 0.75rem or 12px). This can make the tags difficult to read, especially on high-resolution screens. Consider using text-xs to align with the project's typography standards and improve legibility.

                      <span class="border border-gray-200 px-1.5 py-0.5 text-xs leading-none text-gray-500">

{tag}
</span>
))}
</div>
)}
</div>
<ChevronRight class="h-4 w-4 shrink-0 self-center text-gray-400 transition-transform group-hover:translate-x-0.5" />
</a>
</li>
))
}
</ul>
<div class="pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-linear-to-t from-white via-white/80 to-transparent backdrop-blur-[0.5px]" />
</div>

</div>
81 changes: 48 additions & 33 deletions src/components/cards/Thoughts.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,66 @@ import { formatDate } from '../../utils/date'
import { getSortedThoughts } from '../../utils/thoughts'
import CornerMarkers from '../CornerMarkers.astro'
import ArrowRight from '../icons/ArrowRight.astro'
import Lightbulb from '../icons/Lightbulb.astro'
import LightBox from '../LightBox.astro'

const thoughts = await getSortedThoughts()

const latest = thoughts[0]
const Content = latest ? (await render(latest)).Content : null
const hasPreviewImages = (latest?.data.images?.length ?? 0) > 0
---

{latest && (
<div class="relative bg-white border border-gray-200 p-6 h-full flex flex-col transition-all duration-300 hover:border-gray-400">
<div class="relative bg-white border border-gray-200 p-6 h-auto flex flex-col transition-all duration-300 hover:border-gray-400">
<CornerMarkers />

<span class="pointer-events-none absolute right-4 bottom-4 -rotate-10 transform-origin-top-center z-10 border border-gray-300/40 bg-transparent px-3 py-1 font-mono text-xs uppercase tracking-widest text-gray-500/70">
Latest
</span>

<div class="pointer-events-none absolute inset-0 overflow-hidden text-gray-900 opacity-[0.05]">
<Lightbulb class="absolute top-2 left-2 size-20 rotate-20" />
</div>

<div class="mb-4 flex items-center justify-between border-b border-gray-100 pb-4">
<h2 class="text-lg font-semibold text-gray-900 font-mono">Thoughts</h2>
<span class="px-2 py-0.5 text-sm bg-gray-100 text-gray-600 font-mono">
{thoughts.length}
</span>
<h2 class="text-lg font-semibold text-gray-900 font-mono">Thoughts
<span class="px-2 py-0.5 text-sm bg-gray-100 text-gray-600 font-mono">
{thoughts.length}
</span>
</h2>
<div class="flex items-center gap-3">
<a
href="/thoughts"
class="inline-flex items-center gap-1 text-xs text-gray-400 font-mono transition-colors hover:text-gray-600 underline underline-offset-2 decoration-gray-200 hover:decoration-gray-400"
>
View all
<ArrowRight class="h-3 w-3" />
</a>
</div>
</div>

<div class="flex-1">
<article class="prose prose-sm max-w-none text-gray-800 leading-relaxed">
{/* Text content */}
{Content && <Content />}
<div class="flex-1 mb-4">
<div class="relative">
<article class="prose prose-sm prose-a:underline! max-w-none text-gray-800 leading-relaxed">
{/* Text content */}
{Content && <Content />}

{/* Images grid */}
{latest.data.images && latest.data.images.length > 0 && (
<div class="mt-3 grid gap-2 grid-cols-3">
{latest.data.images.map((img: any, index: number) => (
<Image
src={img}
alt={`Thought image ${index + 1}`}
class="border border-gray-200 w-full aspect-square object-cover m-0! cursor-zoom-in transition-all duration-300 hover:border-gray-400 hover:shadow-sm"
data-lightbox="true"
/>
))}
</div>
)}
</article>
{/* Images grid */}
{latest.data.images && latest.data.images.length > 0 && (
<div class="mt-3 grid gap-2 grid-cols-3">
{latest.data.images.map((img: any, index: number) => (
<Image
src={img}
alt={`Thought image ${index + 1}`}
class="border border-gray-200 w-full aspect-square object-cover m-0! cursor-zoom-in transition-all duration-300 hover:border-gray-400 hover:shadow-sm"
data-lightbox="true"
/>
))}
</div>
)}
</article>
</div>

{/* Time & tags below content */}
<div class="mt-3 flex items-center justify-between">
Expand All @@ -58,15 +81,7 @@ const Content = latest ? (await render(latest)).Content : null
)}
</div>
</div>

<div class="mt-auto flex justify-center pt-4 border-t border-gray-100">
<a
href="/thoughts"
class="inline-flex items-center gap-2 px-4 py-3 min-h-[2.75rem] text-sm bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-900 transition-colors font-mono"
>
View more
<ArrowRight class="h-3 w-3" />
</a>
</div>
</div>
)}

{hasPreviewImages && <LightBox />}
Loading