Compare commits
7 Commits
3a929fd268
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 962efd0e75 | |||
| 7ff82151d3 | |||
| 12e61a8d75 | |||
| ad96c06506 | |||
| 497da8038e | |||
| 688788a80d | |||
| 34b6887118 |
@@ -9,6 +9,7 @@ import rehypeExternalLinks from 'rehype-external-links'
|
|||||||
import rehypePrettyCode from 'rehype-pretty-code'
|
import rehypePrettyCode from 'rehype-pretty-code'
|
||||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
||||||
import remarkEmoji from 'remark-emoji'
|
import remarkEmoji from 'remark-emoji'
|
||||||
|
import remarkGithubAlerts from 'remark-github-blockquote-alert'
|
||||||
import remarkMath from 'remark-math'
|
import remarkMath from 'remark-math'
|
||||||
import remarkSectionize from 'remark-sectionize'
|
import remarkSectionize from 'remark-sectionize'
|
||||||
import rehypeDocument from 'rehype-document'
|
import rehypeDocument from 'rehype-document'
|
||||||
@@ -159,6 +160,6 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
remarkPlugins: [remarkMath, remarkEmoji, remarkSectionize],
|
remarkPlugins: [remarkMath, remarkEmoji, remarkSectionize, remarkGithubAlerts],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
"generate:og": "tsx scripts/generate-og-image.ts",
|
"generate:og": "tsx scripts/generate-og-image.ts",
|
||||||
"prettier": "prettier --write **/*.{ts,tsx,css,astro} --ignore-path .gitignore",
|
"prettier": "prettier --write **/*.{ts,tsx,css,astro} --ignore-path .gitignore",
|
||||||
"deploy": "git push origin main:prod"
|
"push:prod": "git push origin main:prod"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.8",
|
"@astrojs/check": "^0.9.8",
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
"rehype-external-links": "^3.0.0",
|
"rehype-external-links": "^3.0.0",
|
||||||
"rehype-pretty-code": "^0.14.3",
|
"rehype-pretty-code": "^0.14.3",
|
||||||
"remark-emoji": "^5.0.2",
|
"remark-emoji": "^5.0.2",
|
||||||
|
"remark-github-blockquote-alert": "^2.1.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"remark-sectionize": "^2.1.0",
|
"remark-sectionize": "^2.1.0",
|
||||||
"satori": "^0.16.2",
|
"satori": "^0.16.2",
|
||||||
|
|||||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -86,6 +86,9 @@ importers:
|
|||||||
remark-emoji:
|
remark-emoji:
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
|
remark-github-blockquote-alert:
|
||||||
|
specifier: ^2.1.0
|
||||||
|
version: 2.1.0
|
||||||
remark-math:
|
remark-math:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
@@ -2235,6 +2238,10 @@ packages:
|
|||||||
remark-gfm@4.0.1:
|
remark-gfm@4.0.1:
|
||||||
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
||||||
|
|
||||||
|
remark-github-blockquote-alert@2.1.0:
|
||||||
|
resolution: {integrity: sha512-J392jmIP684d7iGsENN0uguL10IGbRdc8bTUSrd/jOLzdWkwg721Fj3JPQGN8tF6fTIrE5HHOIA3nBuwuaeuPQ==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
remark-math@6.0.0:
|
remark-math@6.0.0:
|
||||||
resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==}
|
resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==}
|
||||||
|
|
||||||
@@ -5443,6 +5450,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
remark-github-blockquote-alert@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
unist-util-visit: 5.1.0
|
||||||
|
|
||||||
remark-math@6.0.0:
|
remark-math@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
|
|||||||
26
public/_headers
Normal file
26
public/_headers
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
Cache-Control: public, max-age=3600
|
||||||
|
|
||||||
|
/_astro/*
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/fonts/*
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/fonts2/*
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/static/*
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/image/*
|
||||||
|
Cache-Control: public, max-age=2592000
|
||||||
|
|
||||||
|
/*.png
|
||||||
|
Cache-Control: public, max-age=2592000
|
||||||
|
|
||||||
|
/*.ico
|
||||||
|
Cache-Control: public, max-age=2592000
|
||||||
|
|
||||||
|
/*.svg
|
||||||
|
Cache-Control: public, max-age=2592000
|
||||||
@@ -29,9 +29,10 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
entry: EducationEntry
|
entry: EducationEntry
|
||||||
|
isLast?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
let { entry }: Props = $props()
|
let { entry, isLast = false }: Props = $props()
|
||||||
|
|
||||||
const dateDisplay = $derived(() => {
|
const dateDisplay = $derived(() => {
|
||||||
const start = entry.startMonth ? `${entry.startMonth} ${entry.startYear}` : `${entry.startYear}`
|
const start = entry.startMonth ? `${entry.startMonth} ${entry.startYear}` : `${entry.startYear}`
|
||||||
@@ -40,6 +41,18 @@
|
|||||||
const end = entry.endMonth ? `${entry.endMonth} ${entry.endYear}` : `${entry.endYear}`
|
const end = entry.endMonth ? `${entry.endMonth} ${entry.endYear}` : `${entry.endYear}`
|
||||||
return `${start} - ${end}`
|
return `${start} - ${end}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if start date is in the future
|
||||||
|
const now = new Date()
|
||||||
|
const currentYear = now.getFullYear()
|
||||||
|
const isFutureYear = entry.startYear > currentYear
|
||||||
|
const isFutureMonth = entry.startYear === currentYear && entry.startMonth &&
|
||||||
|
new Date(`${entry.startMonth} 1, ${entry.startYear}`).getTime() > now.getTime()
|
||||||
|
|
||||||
|
if (isFutureYear || isFutureMonth) {
|
||||||
|
return `Starting ${start}`
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.expected) {
|
if (entry.expected) {
|
||||||
return `${start} - Present (Expected)`
|
return `${start} - Present (Expected)`
|
||||||
}
|
}
|
||||||
@@ -47,9 +60,9 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative pl-8 pb-8 last:pb-0">
|
<div class="relative pl-8">
|
||||||
<!-- Timeline line -->
|
<!-- Timeline line -->
|
||||||
<div class="absolute left-[7px] top-3 bottom-0 w-0.5 bg-border last:hidden"></div>
|
<div class="absolute left-[7px] top-3 w-0.5 bg-border" class:-bottom-8={!isLast} class:bottom-0={isLast}></div>
|
||||||
|
|
||||||
<!-- Timeline marker -->
|
<!-- Timeline marker -->
|
||||||
<div class="absolute left-0 top-1.5 size-4 rounded-full border-2 border-primary bg-background"></div>
|
<div class="absolute left-0 top-1.5 size-4 rounded-full border-2 border-primary bg-background"></div>
|
||||||
@@ -85,10 +98,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Courses and Awards grid -->
|
<!-- Courses and Awards grid -->
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 sm:gap-4">
|
||||||
<!-- Courses -->
|
<!-- Courses (order-2 on mobile so awards appear first) -->
|
||||||
{#if entry.courses && entry.courses.length > 0}
|
{#if entry.courses && entry.courses.length > 0}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2 order-2 sm:order-1">
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wide">Core Courses</p>
|
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wide">Core Courses</p>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{#each entry.courses as course}
|
{#each entry.courses as course}
|
||||||
@@ -101,9 +114,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Awards -->
|
<!-- Awards (order-1 on mobile so it appears before courses) -->
|
||||||
{#if entry.awards && entry.awards.length > 0}
|
{#if entry.awards && entry.awards.length > 0}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2 order-1 sm:order-2">
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wide">Awards & Scholarships</p>
|
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wide">Awards & Scholarships</p>
|
||||||
<ul class="text-sm space-y-2">
|
<ul class="text-sm space-y-2">
|
||||||
{#each entry.awards as award}
|
{#each entry.awards as award}
|
||||||
|
|||||||
@@ -8,6 +8,6 @@
|
|||||||
let { children }: Props = $props()
|
let { children }: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative flex flex-col gap-6">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { glob } from 'astro/loaders'
|
import { glob } from 'astro/loaders'
|
||||||
import { defineCollection, z } from 'astro:content'
|
import { defineCollection } from 'astro:content'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
const blog = defineCollection({
|
const blog = defineCollection({
|
||||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
|
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
|
||||||
@@ -23,7 +24,7 @@ const projects = defineCollection({
|
|||||||
description: z.string(),
|
description: z.string(),
|
||||||
tags: z.array(z.string()),
|
tags: z.array(z.string()),
|
||||||
image: image(),
|
image: image(),
|
||||||
link: z.string().url().optional(),
|
link: z.url().optional(),
|
||||||
order: z.number().optional(),
|
order: z.number().optional(),
|
||||||
startDate: z.coerce.date().optional(),
|
startDate: z.coerce.date().optional(),
|
||||||
endDate: z.coerce.date().optional(),
|
endDate: z.coerce.date().optional(),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ What if you could block ads and trackers across your entire network, on every de
|
|||||||
|
|
||||||
That's what we'll build in this guide. There are already a few [ready-made solutions](#part-11-where-to-go-from-here---pi-hole-and-adguard-home), but building a simple system ourselves will help us understand how the underlying logic actually works. By the end, you'll have a working network-level ad-blocker running on a Debian machine, and a solid understanding of the DNS system that makes it all possible.
|
That's what we'll build in this guide. There are already a few [ready-made solutions](#part-11-where-to-go-from-here---pi-hole-and-adguard-home), but building a simple system ourselves will help us understand how the underlying logic actually works. By the end, you'll have a working network-level ad-blocker running on a Debian machine, and a solid understanding of the DNS system that makes it all possible.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
> **Prerequisites**: A machine running Debian (this guide uses Debian 13 Trixie), basic comfort with the terminal, and a home network you control. You could also follow along up to Part 5 on Windows or Mac.
|
> **Prerequisites**: A machine running Debian (this guide uses Debian 13 Trixie), basic comfort with the terminal, and a home network you control. You could also follow along up to Part 5 on Windows or Mac.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -106,6 +107,7 @@ Add a line like this to your hosts file:
|
|||||||
0.0.0.0 facebook.com
|
0.0.0.0 facebook.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
> If your network uses IPv6, you may also need to add the line `:: facebook.com` to block the IPv6 address. `::` is the IPv6 equivalent of `0.0.0.0`. We'll assume we're on an IPv4 network for the remainder of this post.
|
> If your network uses IPv6, you may also need to add the line `:: facebook.com` to block the IPv6 address. `::` is the IPv6 equivalent of `0.0.0.0`. We'll assume we're on an IPv4 network for the remainder of this post.
|
||||||
|
|
||||||
Save the file and try it:
|
Save the file and try it:
|
||||||
@@ -116,11 +118,13 @@ curl -I facebook.com
|
|||||||
|
|
||||||
You should see a "connection refused" error, which is exactly what we want. The address resolves to `0.0.0.0`, nothing is listening there, and the connection fails. Alternatively, try visiting `facebook.com` in your browser — you'll get an error page instead of the site.
|
You should see a "connection refused" error, which is exactly what we want. The address resolves to `0.0.0.0`, nothing is listening there, and the connection fails. Alternatively, try visiting `facebook.com` in your browser — you'll get an error page instead of the site.
|
||||||
|
|
||||||
> **Note:** This same mechanism is what makes DNS poisoning attacks possible. If an attacker can modify your hosts file or intercept your DNS queries, they can redirect `facebook.com` to a malicious server that looks identical to the real thing. This is one reason why HTTPS and certificate validation matter: even if DNS is compromised, a valid TLS certificate is hard to fake.
|
> [!WARNING]
|
||||||
|
> This same mechanism is what makes DNS poisoning attacks possible. If an attacker can modify your hosts file or intercept your DNS queries, they can redirect `facebook.com` to a malicious server that looks identical to the real thing. This is one reason why HTTPS and certificate validation matter: even if DNS is compromised, a valid TLS certificate is hard to fake.
|
||||||
|
|
||||||
Remove the `facebook.com` line when you're done experimenting.
|
Remove the `facebook.com` line when you're done experimenting.
|
||||||
|
|
||||||
> **Note:** You may notice that pinging `0.0.0.0` actually reaches the loopback address `127.0.0.1`. This is because RFC 1122 specifies that `0.0.0.0` is only valid as a source address. On most Unix-like systems, using it as a destination causes the OS to treat it as the local loopback instead. Keep this in mind if you have a local web server running, as it may respond to requests directed at these blocked domains.
|
> [!NOTE]
|
||||||
|
> You may notice that pinging `0.0.0.0` actually reaches the loopback address `127.0.0.1`. This is because RFC 1122 specifies that `0.0.0.0` is only valid as a source address. On most Unix-like systems, using it as a destination causes the OS to treat it as the local loopback instead. Keep this in mind if you have a local web server running, as it may respond to requests directed at these blocked domains.
|
||||||
---
|
---
|
||||||
|
|
||||||
## Part 4: Using the Hosts File to Block Ads
|
## Part 4: Using the Hosts File to Block Ads
|
||||||
@@ -240,6 +244,7 @@ bogus-priv
|
|||||||
addn-hosts=/etc/hosts.blocklist
|
addn-hosts=/etc/hosts.blocklist
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
> **Find your interface name** with `ip link`. It's usually `eth0` for wired connections, but modern Debian systems often use names like `enp3s0` or `ens3`.
|
> **Find your interface name** with `ip link`. It's usually `eth0` for wired connections, but modern Debian systems often use names like `enp3s0` or `ens3`.
|
||||||
|
|
||||||
### Restarting dnsmasq
|
### Restarting dnsmasq
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
title: "Deploying to Cloudflare Pages with GitHub Actions"
|
title: "Deploying to Cloudflare Pages with GitHub Actions"
|
||||||
description: "A simple CI/CD workflow for deploying static site to Cloudflare Pages using GitHub Actions"
|
description: "A simple CI/CD workflow for deploying static site to Cloudflare Pages using GitHub Actions"
|
||||||
date: 2026-03-26
|
date: 2026-03-26
|
||||||
|
updatedDate:
|
||||||
tags: ['CI/CD', 'Gitea', 'Cloudflare Pages', 'DevOps']
|
tags: ['CI/CD', 'Gitea', 'Cloudflare Pages', 'DevOps']
|
||||||
authors:
|
authors:
|
||||||
- "Patrick Jaroszewski"
|
- "Patrick Jaroszewski"
|
||||||
@@ -16,9 +17,20 @@ This post walks through the workflow I use to deploy this site and others I host
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before getting started, you'll need:
|
||||||
|
|
||||||
|
- A Gitea instance with Actions enabled ([documentation](https://docs.gitea.com/usage/actions/overview))
|
||||||
|
- A registered Gitea runner (ubuntu-latest or equivalent)
|
||||||
|
- A Cloudflare account with access to Pages
|
||||||
|
- Your project set up with pnpm (adjust commands for npm/yarn as needed)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## The Basic Workflow
|
## The Basic Workflow
|
||||||
|
|
||||||
Here's a minimal Gitea Actions workflow that builds an Astro site and deploys it to Cloudflare Pages:
|
Here's a minimal Gitea Actions workflow that builds an Astro site and deploys it to Cloudflare Pages. Note that Gitea uses `.gitea/workflows/` instead of GitHub's `.github/workflows/`.
|
||||||
|
|
||||||
```yaml title=".gitea/workflows/deploy.yaml"
|
```yaml title=".gitea/workflows/deploy.yaml"
|
||||||
name: Generate a build and push to Cloudflare Pages
|
name: Generate a build and push to Cloudflare Pages
|
||||||
@@ -64,6 +76,21 @@ jobs:
|
|||||||
|
|
||||||
The workflow triggers on pushes to `main`, installs dependencies, builds the site, and deploys using Cloudflare's Wrangler action. Every push to main goes straight to production.
|
The workflow triggers on pushes to `main`, installs dependencies, builds the site, and deploys using Cloudflare's Wrangler action. Every push to main goes straight to production.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Adjust `./dist` to match your framework's output directory—this is Astro's default. For example, Next.js static exports use `out`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Secrets
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If your Cloudflare Pages project doesn't exist yet, Wrangler will create it automatically on the first deployment.
|
||||||
|
|
||||||
|
You'll need to configure two secrets in your Gitea repository (Settings → Actions → Secrets):
|
||||||
|
|
||||||
|
- `CLOUDFLARE_API_TOKEN` — Create this in the Cloudflare dashboard: Profile → API Tokens → Create Token → Use the "Edit Cloudflare Pages" template (or create a custom token with **Account: Cloudflare Pages: Edit** permission)
|
||||||
|
- `CLOUDFLARE_ACCOUNT_ID` — Found in the Cloudflare dashboard sidebar under **Workers & Pages → Overview**, or in the URL when viewing any Pages project (`dash.cloudflare.com/<account-id>/pages`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Using a Separate Production Branch
|
## Using a Separate Production Branch
|
||||||
@@ -89,12 +116,12 @@ Then add a script to promote `main` to `prod` when you're ready:
|
|||||||
```json title="package.json"
|
```json title="package.json"
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "git push origin main:prod"
|
"push:prod": "git push origin main:prod"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you develop freely on `main`, and run `pnpm deploy` when you want to go live.
|
Now you develop freely on `main`, and run `pnpm push:prod` when you want to go live.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -103,7 +130,7 @@ Now you develop freely on `main`, and run `pnpm deploy` when you want to go live
|
|||||||
If you use a `prod` branch, there's a catch: Cloudflare Pages uses the **branch name** to determine the deployment environment.
|
If you use a `prod` branch, there's a catch: Cloudflare Pages uses the **branch name** to determine the deployment environment.
|
||||||
|
|
||||||
- `main` or `master` → production deployment (your primary URL)
|
- `main` or `master` → production deployment (your primary URL)
|
||||||
- Any other branch → preview deployment (e.g., `prod.your-project.pages.dev`)
|
- Any other branch → preview deployment (e.g., `branch-name.your-project.pages.dev`)
|
||||||
|
|
||||||
If you deploy from a branch called `prod`, Cloudflare treats it as a preview deployment, not production. Your site ends up at a subdomain instead of your main URL.
|
If you deploy from a branch called `prod`, Cloudflare treats it as a preview deployment, not production. Your site ends up at a subdomain instead of your main URL.
|
||||||
|
|
||||||
@@ -121,12 +148,28 @@ Option 1 is cleaner because it aligns Cloudflare's understanding with your actua
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Required Secrets
|
## Using a Preview Branch
|
||||||
|
|
||||||
You'll need to configure two secrets in your Gitea repository (Settings → Actions → Secrets):
|
Since Cloudflare treats all other branches as preview deployments, you can easily deploy a preview of your site by triggering the workflow on any branch (e.g., `preview`).
|
||||||
|
|
||||||
- `CLOUDFLARE_API_TOKEN` — Create an API token in Cloudflare with "Cloudflare Pages: Edit" permissions
|
```yaml
|
||||||
- `CLOUDFLARE_ACCOUNT_ID` — Found in your Cloudflare dashboard URL or account settings
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- prod
|
||||||
|
- preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Pushing to the `preview` branch deploys your site to `preview.your-project.pages.dev`. To make this convenient, add another script to your `package.json`:
|
||||||
|
|
||||||
|
```json title="package.json"
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"push:prod": "git push origin main:prod",
|
||||||
|
"push:preview": "git push origin main:preview"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
12
src/content/projects/cue.mdx
vendored
12
src/content/projects/cue.mdx
vendored
@@ -18,9 +18,15 @@ startDate: '2025-09-23'
|
|||||||
|
|
||||||
Cue is an on-demand resource booking system built for an arcade and pool venue. The UI was designed to run on a terminal-like POS touchscreen.
|
Cue is an on-demand resource booking system built for an arcade and pool venue. The UI was designed to run on a terminal-like POS touchscreen.
|
||||||
|
|
||||||
<video autoplay loop muted playsinline preload="auto" data-astro-reload>
|
<video
|
||||||
<source src="/static/cue.webm" type="video/webm" />
|
src="/static/cue.webm"
|
||||||
</video>
|
autoplay
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
preload="auto"
|
||||||
|
class="w-full"
|
||||||
|
></video>
|
||||||
|
|
||||||
## The Problem
|
## The Problem
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import type { EducationEntry } from '@/components/svelte/EducationItem.svelte'
|
import type { EducationEntry } from '@/components/svelte/EducationItem.svelte'
|
||||||
|
|
||||||
export const education: EducationEntry[] = [
|
export const education: EducationEntry[] = [
|
||||||
|
{
|
||||||
|
school: 'Georgia Institute of Technology',
|
||||||
|
degree: 'Masters of Computer Science',
|
||||||
|
location: 'Atlanta, USA',
|
||||||
|
startYear: 2026,
|
||||||
|
startMonth: 'Sept',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
school: 'University of Guelph',
|
school: 'University of Guelph',
|
||||||
degree: 'Bachelor of Computing',
|
degree: 'Bachelor of Computing',
|
||||||
@@ -12,7 +19,7 @@ export const education: EducationEntry[] = [
|
|||||||
endYear: 2024,
|
endYear: 2024,
|
||||||
endMonth: 'May',
|
endMonth: 'May',
|
||||||
expected: false,
|
expected: false,
|
||||||
courses: ['Data Structures', 'Algorithms', 'Software Engineering', 'Operating Systems', 'Databases', 'Computer Networks'],
|
courses: ['Data Structures', 'Algorithms', 'Software Engineering', 'Operating Systems', 'Databases', 'Computer Networks', 'Cloud Computing', 'Systems Programming'],
|
||||||
awards: [
|
awards: [
|
||||||
{ name: "Dean's List", description: 'In each full-time semester' },
|
{ name: "Dean's List", description: 'In each full-time semester' },
|
||||||
{ name: 'Weiner Mathematical Scholarship', description: 'Highest mathematics average (1st year)' },
|
{ name: 'Weiner Mathematical Scholarship', description: 'Highest mathematics average (1st year)' },
|
||||||
|
|||||||
@@ -172,8 +172,8 @@ const turnstileSitekey = import.meta.env.PUBLIC_TURNSTILE_SITEKEY
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<Timeline client:load>
|
<Timeline client:load>
|
||||||
{education.map(entry => (
|
{education.map((entry, i) => (
|
||||||
<EducationItem entry={entry} client:load />
|
<EducationItem entry={entry} isLast={i === education.length - 1} client:load />
|
||||||
))}
|
))}
|
||||||
</Timeline>
|
</Timeline>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -83,6 +83,36 @@
|
|||||||
@apply [&>p]:my-0 [&>p:first-child]:mt-0 [&>p:last-child]:mb-0;
|
@apply [&>p]:my-0 [&>p:first-child]:mt-0 [&>p:last-child]:mb-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* GitHub-style admonitions */
|
||||||
|
.markdown-alert {
|
||||||
|
@apply my-4 rounded-md border-l-4 px-4 py-2 not-italic;
|
||||||
|
@apply [&>p]:my-0 [&>p:not(.markdown-alert-title)]:mt-1;
|
||||||
|
}
|
||||||
|
.markdown-alert-title {
|
||||||
|
@apply flex items-center gap-2 font-semibold text-sm;
|
||||||
|
@apply [&>svg]:size-4 [&>svg]:fill-current;
|
||||||
|
}
|
||||||
|
.markdown-alert-note {
|
||||||
|
@apply border-blue-500 bg-blue-500/10;
|
||||||
|
@apply [&>.markdown-alert-title]:text-blue-600 dark:[&>.markdown-alert-title]:text-blue-400;
|
||||||
|
}
|
||||||
|
.markdown-alert-tip {
|
||||||
|
@apply border-green-500 bg-green-500/10;
|
||||||
|
@apply [&>.markdown-alert-title]:text-green-600 dark:[&>.markdown-alert-title]:text-green-400;
|
||||||
|
}
|
||||||
|
.markdown-alert-important {
|
||||||
|
@apply border-purple-500 bg-purple-500/10;
|
||||||
|
@apply [&>.markdown-alert-title]:text-purple-600 dark:[&>.markdown-alert-title]:text-purple-400;
|
||||||
|
}
|
||||||
|
.markdown-alert-warning {
|
||||||
|
@apply border-yellow-500 bg-yellow-500/10;
|
||||||
|
@apply [&>.markdown-alert-title]:text-yellow-600 dark:[&>.markdown-alert-title]:text-yellow-400;
|
||||||
|
}
|
||||||
|
.markdown-alert-caution {
|
||||||
|
@apply border-red-500 bg-red-500/10;
|
||||||
|
@apply [&>.markdown-alert-title]:text-red-600 dark:[&>.markdown-alert-title]:text-red-400;
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
@apply border-border my-10 border-t-2;
|
@apply border-border my-10 border-t-2;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user