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
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ source "https://rubygems.org"
ruby RUBY_VERSION

gem "jekyll"
gem "minimal-mistakes-jekyll"

group :jekyll_plugins do
gem "jekyll-paginate"
gem "jekyll-sitemap"
gem "jekyll-gist"
gem "jekyll-feed"
gem "jekyll-include-cache"
gem "jekyll-octicons"
end
15 changes: 2 additions & 13 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ description: Software developer and technical writer based in Vancouver, Canada.
baseurl: ""
url: "http://alphang.com"

# Theme
theme: minimal-mistakes-jekyll
minimal_mistakes_skin: "dark"

# Build settings
markdown: kramdown
highlighter: rouge
Expand All @@ -19,7 +15,6 @@ plugins:
- jekyll-sitemap
- jekyll-gist
- jekyll-feed
- jekyll-include-cache
- jekyll-octicons

# Pagination
Expand All @@ -34,10 +29,8 @@ author:
avatar: "/assets/brick-road-alpha.jpg"
links:
- label: "GitHub"
icon: "fab fa-fw fa-github"
url: "https://github.com/alphang"
- label: "LinkedIn"
icon: "fab fa-fw fa-linkedin"
url: "https://www.linkedin.com/in/alphang"

# Defaults
Expand All @@ -46,16 +39,12 @@ defaults:
path: ""
type: posts
values:
layout: single
author_profile: true
read_time: true
share: false
layout: post
- scope:
path: ""
type: pages
values:
layout: single
author_profile: true
layout: page

exclude:
- CLAUDE.md
Expand Down
184 changes: 184 additions & 0 deletions _layouts/default.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% if page.title %}{{ page.title }} — {% endif %}{{ site.title }}</title>
<meta name="description" content="{{ page.description | default: site.description }}">
<link rel="canonical" href="{{ page.url | absolute_url }}">
{% feed_meta %}

<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap" rel="stylesheet">

<!-- Tailwind CSS Play CDN -->
<script src="https://cdn.tailwindcss.com"></script>

<style type="text/tailwindcss">
@layer base {
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
code, pre {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
}
::selection {
@apply bg-violet-500/25 text-violet-100;
}
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { @apply bg-slate-950; }
::-webkit-scrollbar-thumb { @apply bg-slate-700 rounded-full; }
::-webkit-scrollbar-thumb:hover { @apply bg-slate-600; }
}

@layer components {
.gradient-text {
@apply bg-gradient-to-r from-violet-400 to-cyan-400 bg-clip-text text-transparent;
}
.nav-link {
@apply relative text-sm text-slate-400 hover:text-slate-100 transition-colors duration-200;
}
.nav-link::after {
content: '';
@apply absolute -bottom-0.5 left-0 w-0 h-px bg-violet-400 transition-all duration-300;
}
.nav-link:hover::after {
@apply w-full;
}
.card {
@apply bg-slate-800/40 border border-slate-700/50 rounded-xl p-6
hover:bg-slate-800/70 hover:border-slate-600/70
hover:shadow-xl hover:shadow-slate-900/50
transition-all duration-300 cursor-pointer;
}
.tag {
@apply text-xs font-medium text-violet-400 bg-violet-500/10 border border-violet-500/20 px-2.5 py-0.5 rounded-full;
}

/* Prose styles for rendered Markdown content */
.prose-custom { @apply text-slate-300 leading-relaxed; }
.prose-custom h1 { @apply text-2xl md:text-3xl font-bold text-slate-50 mt-10 mb-4 tracking-tight; }
.prose-custom h2 { @apply text-xl md:text-2xl font-semibold text-slate-100 mt-8 mb-3; }
.prose-custom h3 { @apply text-lg font-semibold text-slate-200 mt-6 mb-2; }
.prose-custom h4 { @apply text-base font-semibold text-slate-200 mt-4 mb-1; }
.prose-custom p { @apply mb-5 text-slate-400 leading-[1.85]; }
.prose-custom a { @apply text-violet-400 hover:text-violet-300 underline underline-offset-2 decoration-violet-500/40 hover:decoration-violet-400/70 transition-colors; }
.prose-custom ul { @apply list-disc pl-6 mb-5 space-y-1.5 text-slate-400; }
.prose-custom ol { @apply list-decimal pl-6 mb-5 space-y-1.5 text-slate-400; }
.prose-custom li { @apply leading-relaxed; }
.prose-custom li > p { @apply mb-0; }
.prose-custom blockquote { @apply border-l-2 border-violet-500 pl-5 italic text-slate-400 my-6; }
.prose-custom blockquote p { @apply mb-0; }
.prose-custom code { @apply bg-slate-800 text-violet-300 px-1.5 py-0.5 rounded text-[0.85em]; }
.prose-custom pre { @apply bg-slate-950 border border-slate-800 rounded-xl p-5 overflow-x-auto my-6; }
.prose-custom pre code { @apply bg-transparent text-slate-300 p-0; }
.prose-custom img { @apply rounded-xl my-6 w-full; }
.prose-custom hr { @apply border-slate-800 my-8; }
.prose-custom strong { @apply text-slate-200 font-semibold; }
.prose-custom em { @apply text-slate-300; }
.prose-custom table { @apply w-full border-collapse my-6 text-sm; }
.prose-custom th { @apply text-left px-4 py-2.5 bg-slate-800 text-slate-200 font-semibold border border-slate-700; }
.prose-custom td { @apply px-4 py-2.5 text-slate-400 border border-slate-700; }
}

@layer utilities {
.animate-fade-in {
animation: fadeIn 0.5s ease-out both;
}
.animate-fade-up {
animation: fadeUp 0.6s ease-out both;
}
.animate-fade-up-delay {
animation: fadeUp 0.6s ease-out 0.12s both;
}
.animate-on-scroll {
opacity: 0;
transform: translateY(14px);
transition: opacity 0.55s ease-out, transform 0.55s ease-out;
}
.animate-on-scroll.visible {
opacity: 1;
transform: translateY(0);
}
}

@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}

/* Navbar blur */
.navbar {
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
}
</style>
</head>
<body class="bg-slate-900 text-slate-200 antialiased">

<!-- Navigation -->
<header class="navbar fixed top-0 inset-x-0 z-50 bg-slate-900/75 border-b border-slate-800/70">
<div class="max-w-4xl mx-auto px-5 md:px-8 h-16 flex items-center justify-between">
<a href="{{ site.baseurl }}/" class="font-semibold text-[1.05rem] tracking-tight hover:opacity-75 transition-opacity duration-200">
<span class="gradient-text">{{ site.title }}</span>
</a>
<nav class="flex items-center gap-7">
{% for link in site.data.navigation.main %}
<a href="{{ link.url | prepend: site.baseurl }}" class="nav-link">{{ link.title }}</a>
{% endfor %}
</nav>
</div>
</header>

<!-- Page content -->
<main class="min-h-screen pt-16">
{{ content }}
</main>

<!-- Footer -->
<footer class="border-t border-slate-800 mt-20">
<div class="max-w-4xl mx-auto px-5 md:px-8 py-10 flex flex-col sm:flex-row items-center justify-between gap-4">
<p class="text-slate-500 text-sm">
&copy; {{ site.time | date: "%Y" }} <span class="text-slate-400">{{ site.author.name }}</span>
</p>
<div class="flex items-center gap-5">
{% for link in site.author.links %}
<a href="{{ link.url }}" target="_blank" rel="noopener noreferrer"
class="text-slate-500 hover:text-slate-300 transition-colors duration-200 text-sm">
{{ link.label }}
</a>
{% endfor %}
</div>
</div>
</footer>

<!-- Scroll-reveal for .animate-on-scroll elements -->
<script>
(function () {
var obs = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
var delay = parseInt(entry.target.dataset.delay) || 0;
setTimeout(function () {
entry.target.classList.add('visible');
}, delay);
obs.unobserve(entry.target);
}
});
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });

document.querySelectorAll('.animate-on-scroll').forEach(function (el, i) {
el.dataset.delay = i * 70;
obs.observe(el);
});
})();
</script>

</body>
</html>
116 changes: 116 additions & 0 deletions _layouts/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
layout: default
---

<div class="max-w-4xl mx-auto px-5 md:px-8">

<!-- Hero -->
<section class="py-16 md:py-24 animate-fade-in">
{% if site.author.avatar %}
<div class="flex items-center gap-3 mb-7">
<img src="{{ site.author.avatar }}" alt="{{ site.author.name }}"
class="w-11 h-11 rounded-full object-cover ring-2 ring-violet-500/40 ring-offset-2 ring-offset-slate-900">
<div>
<p class="text-[0.7rem] uppercase tracking-widest text-slate-500 font-medium">Author</p>
<p class="text-sm text-slate-300 font-medium">{{ site.author.name }}</p>
</div>
</div>
{% endif %}

<h1 class="text-4xl md:text-5xl font-bold leading-[1.15] tracking-tight mb-5">
<span class="gradient-text">Software developer</span><br>
<span class="text-slate-100">&amp; technical writer.</span>
</h1>
<p class="text-slate-400 text-lg max-w-md leading-relaxed">
{{ site.description }}
</p>
</section>

<!-- Posts -->
<section class="pb-24">
<h2 class="flex items-center gap-4 text-[0.7rem] font-semibold uppercase tracking-widest text-slate-500 mb-8">
Latest Posts
<span class="flex-1 h-px bg-slate-800"></span>
</h2>

{% if paginator.posts.size > 0 %}
<div class="space-y-4">
{% for post in paginator.posts %}
<article class="card animate-on-scroll group"
onclick="window.location='{{ post.url | prepend: site.baseurl }}'">
<div class="flex flex-col sm:flex-row sm:items-center gap-4">
<div class="flex-1 min-w-0">
<div class="flex flex-wrap items-center gap-x-2 gap-y-1 mb-2.5">
<time class="text-xs text-slate-500" datetime="{{ post.date | date_to_xmlschema }}">
{{ post.date | date: "%b %-d, %Y" }}
</time>
{% if post.categories.size > 0 %}
<span class="text-slate-700 text-xs">·</span>
{% for cat in post.categories limit:2 %}
<span class="tag">{{ cat }}</span>
{% endfor %}
{% endif %}
</div>

<h3 class="text-base md:text-lg font-semibold text-slate-100 mb-2
group-hover:text-violet-300 transition-colors duration-200">
{{ post.title }}
</h3>

{% if post.excerpt %}
<p class="text-sm text-slate-500 leading-relaxed line-clamp-2">
{{ post.excerpt | strip_html | truncate: 150 }}
</p>
{% endif %}
</div>

<div class="flex-shrink-0">
<span class="inline-flex items-center gap-1 text-xs font-medium text-violet-400/70
group-hover:text-violet-300 group-hover:gap-2 transition-all duration-200">
Read
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</span>
</div>
</div>
</article>
{% endfor %}
</div>

{% if paginator.total_pages > 1 %}
<nav class="flex items-center justify-center gap-3 mt-12" aria-label="Pagination">
{% if paginator.previous_page %}
<a href="{{ paginator.previous_page_path | prepend: site.baseurl }}"
class="flex items-center gap-1.5 px-4 py-2 text-sm text-slate-400
hover:text-slate-200 border border-slate-700 hover:border-slate-600
rounded-lg transition-all duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
Newer
</a>
{% endif %}
<span class="text-sm text-slate-600 px-2">{{ paginator.page }} of {{ paginator.total_pages }}</span>
{% if paginator.next_page %}
<a href="{{ paginator.next_page_path | prepend: site.baseurl }}"
class="flex items-center gap-1.5 px-4 py-2 text-sm text-slate-400
hover:text-slate-200 border border-slate-700 hover:border-slate-600
rounded-lg transition-all duration-200">
Older
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</a>
{% endif %}
</nav>
{% endif %}

{% else %}
<div class="py-20 text-center animate-fade-in">
<p class="text-slate-600 text-sm">No posts yet — check back soon.</p>
</div>
{% endif %}

</section>
</div>
18 changes: 18 additions & 0 deletions _layouts/page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
layout: default
---

<div class="max-w-3xl mx-auto px-5 md:px-8 py-16">

<header class="mb-10 animate-fade-in">
<h1 class="text-3xl md:text-4xl font-bold text-slate-50 tracking-tight mb-5">
{{ page.title }}
</h1>
<div class="h-px bg-gradient-to-r from-violet-500/50 via-cyan-500/20 to-transparent"></div>
</header>

<div class="prose-custom animate-fade-up">
{{ content }}
</div>

</div>
Loading