2026-02-25T02-06-19_auto_memory/memories.db-wal

This commit is contained in:
Nicholai Vogel 2026-02-24 19:06:19 -07:00
parent 6fe7cb2475
commit ef6b36855e
8 changed files with 740 additions and 0 deletions

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,200 @@
---
name: astro-portfolio-site
description: "Build a complete indie studio, small business, or creative portfolio website from a client brief and brand assets. Produces a production-ready Astro 5 + React 19 + Tailwind CSS 4 + GSAP site deployed to Cloudflare Pages. Use when the user wants to build a new website, portfolio, studio site, or marketing site for a client — or when they provide a company name, brand assets, and general idea for a new site. Also use when asked to scaffold, set up, or create a new Astro website project."
---
# Astro Portfolio Site Builder
Build production-ready portfolio websites from a client brief. Stack: Astro 5, React 19, Tailwind CSS 4, GSAP, TypeScript, Cloudflare Pages, Bun.
## Intake Checklist
Before starting, gather from the client brief:
**Required:**
- Company/studio name and tagline
- Site description (1-2 sentences for SEO)
- Contact email
- Primary brand color (hex) + 1-2 accent colors
- Logo file(s) (SVG preferred + PNG fallback)
**Recommended:**
- Social links (Discord, Twitter/X, Steam, Instagram, GitHub, etc.)
- Font preferences (display/heading + body). Default: pixel font + clean sans-serif
- Hero background (video mp4 or image)
- OG/social share image (1200x630)
- Favicon set (SVG + ICO + PNG 32/192 + apple-touch)
**Optional:**
- Product/game details (for product page + structured data)
- Team member info
- Press kit assets
- Existing blog content or content plan
- Custom domain (for astro.config site URL)
- Resend API key (for contact form emails)
## Phase 1: Project Scaffold
1. Create project directory and initialize:
```bash
mkdir <project-name> && cd <project-name>
bun create astro@latest . -- --template minimal --no-install
bun install
```
2. Install dependencies:
```bash
bun add @astrojs/cloudflare @astrojs/mdx @astrojs/react @astrojs/rss @astrojs/sitemap \
@react-email/components @tailwindcss/typography @tailwindcss/vite \
clsx fuse.js gsap react react-dom react-icons resend sharp tailwind-merge tailwindcss
bun add -d @types/react @types/react-dom @types/node wrangler
```
3. Create directory structure — see `assets/scaffold-dirs.txt`
4. Write config files — see `references/stack-config.md` for exact patterns:
- `astro.config.mjs` (site URL, integrations, cloudflare adapter, vite tailwindcss plugin)
- `tsconfig.json` (strict, path aliases, react-jsx)
- `wrangler.jsonc` (project name, compatibility date, nodejs_compat, vars)
- `src/consts.ts` (SITE_TITLE, SITE_DESCRIPTION, HTML_MARKER, SOCIAL_LINKS)
- `src/env.d.ts` (Cloudflare Runtime type + Env interface)
- `src/lib/utils.ts` (cn function: clsx + tailwind-merge)
## Phase 2: Design System
Read `references/design-system.md` for complete CSS structure.
1. Place custom fonts in `public/assets/fonts/` (woff2 + ttf fallback)
2. Create `src/styles/global.css` with:
- `@import "tailwindcss"` (must be first line)
- `@font-face` declarations with `font-display: swap`
- `@custom-variant dark` and `@plugin "@tailwindcss/typography"`
- `@theme {}` block mapping client colors to token names
- Font stacks: `--font-display`, `--font-body`, `--font-mono`
- Pixel shadow tokens and glow tokens
3. Add global element styles (body, headings, code)
4. Add custom cursor styles (optional — skip for non-gaming sites)
5. Add scrollbar theming with brand colors
6. Add utility classes: `.text-glow-*`, `.box-pixel*`, `.pixel-art`, `.scrollbar-none`, `.scanlines`, `.crt-screen`
7. Add keyframe animations + `prefers-reduced-motion` resets
8. Add safe viewport height utilities (`.h-screen-safe`, `.min-h-screen-safe`)
## Phase 3: Layouts & Base Components
Read `references/seo-structured-data.md` for JSON-LD and meta patterns.
1. **StructuredData.astro** — generic JSON-LD `<script>` renderer
2. **FormattedDate.astro**`<time>` element with locale formatting
3. **BaseHead.astro** — meta tags, favicons, font preloads, Google Fonts, canonical URL, RSS link, OG/Twitter cards, robots meta
4. **BaseLayout.astro** — wraps BaseHead + Organization JSON-LD + CustomCursor (optional) + `<slot />`
5. **BlogPost.astro** — Article + Breadcrumb JSON-LD, TOC sidebar (desktop sticky, mobile `<details>`), hero image, prose typography
## Phase 4: Core Components
Read `references/component-patterns.md` for GSAP patterns and hydration rules.
Build these React components:
1. **Navigation.tsx** (`client:load`) — fixed header, scroll detection, fullscreen overlay menu, GSAP animations, ESC to close, body overflow lock
2. **Hero.tsx** (`client:load`) — full viewport, video/image background, entrance animations (sessionStorage skip on repeat), parallax, CTAs
3. **Footer.tsx** (`client:visible`) — multi-column grid (brand, nav, legal, social), copyright
4. **Contact.tsx** (`client:visible`) — two-column (info + form), honeypot, status states, GSAP ScrollTrigger entrance
5. **Loader.tsx** (`client:load`, optional) — one-time splash, sessionStorage, progress bar, safety timeout, click-to-skip
6. **CustomCursor.tsx** (`client:load`, optional) — GSAP smooth follow, mix-blend-mode, hidden on touch
7. **Marquee.tsx** (`client:visible`, optional) — scrolling text ticker
### GSAP Rules (apply to all animated components)
- Wrap in `gsap.context()` with `return () => ctx.revert()` cleanup in useEffect
- Check `prefers-reduced-motion` and bail early if reduced
- Transform-only properties (x, y, scale, rotation, opacity) for GPU acceleration
- SessionStorage for one-time entrance animations
- ScrollTrigger for below-fold entrance reveals
- Stagger timelines with negative relative positioning (`'-=0.6'`)
## Phase 5: Pages
1. **index.astro** — compose: Loader + Nav + Hero + sections + Contact + Footer + Marquee
2. **about.astro** — static info page
3. **contact.astro** — dedicated contact page
4. Product page (if applicable) — with Product/VideoGame schema
5. **press.astro** (optional) — press kit
6. **privacy.astro**, **terms.astro** — legal pages (`robots="noindex, follow"`)
7. **404.astro** — custom not-found (`robots="noindex, follow"`)
## Phase 6: Blog Infrastructure
Read `references/blog-infrastructure.md` for all patterns.
1. `src/content.config.ts` — blog collection (Content Layer API, glob loader, Zod schema with `image()`)
2. `src/pages/blog/index.astro` — featured post, category filter, post grid, sidebar
3. `src/pages/blog/[...slug].astro` — dynamic post route with `getStaticPaths()`
4. `src/pages/blog/tag/[tag].astro` — tag archive pages
5. `src/pages/blog/category/[category].astro` — category archive pages
6. Blog components: BlogCard.astro, FeaturedPosts.astro, BlogSearch.tsx (`client:idle`)
7. `src/pages/search.json.ts` (prerender: true) — Fuse.js search index
8. `src/utils/reading-time.ts` — word count calculator
9. Sample blog post with correct frontmatter
## Phase 7: Contact Form Backend
Read `references/contact-form-system.md` for exact patterns.
1. `src/pages/api/contact.ts` — POST endpoint: rate limiting, honeypot, validation, Resend dual email
2. Email templates in `src/emails/`: notification + confirmation (React Email, dark theme, inline styles)
3. Wrangler vars: `CONTACT_EMAIL`. Secret: `RESEND_API_KEY` via `wrangler secret put`
4. Rate limiting: Cloudflare dashboard only (not wrangler config)
## Phase 8: SEO & Data Endpoints
Read `references/seo-structured-data.md` for code patterns.
1. `public/robots.txt` — allow all, LLM-Policy, sitemap URL
2. `src/pages/rss.xml.ts` (prerender: true) — @astrojs/rss feed
3. `src/pages/llms.txt.ts` (prerender: true) — page/post index for LLMs
4. `src/pages/llms-full.txt.ts` (prerender: true) — full blog content for LLMs
5. Verify sitemap filters out 404, privacy, terms, shop
## Phase 9: Assets & Images
1. Favicon set in `public/`: favicon.svg, .ico, -32.png, -192.png, apple-touch-icon.png
2. OG fallback at `public/og-default.jpg` (1200x630)
3. Logos in `public/assets/images/logos/`
4. Fonts in `public/assets/fonts/`
5. Copy AVIF conversion script to `src/utils/convert-to-avif.js`
6. Copy AI commit helper to `src/utils/git-commit.js` + `.env.example`
7. Use `.pixel-art` class on pixel art images
## Phase 10: External API Integration (if applicable)
If the client has an external data source (Steam, Shopify, etc.):
1. Create `src/lib/<service>.ts` with typed interfaces
2. `fetchJson<T>()` wrapper returning `T | null` on failure
3. Parallel fetch with `Promise.all`, null fields on partial failure
4. Wire into pages via frontmatter `await`, pass as component props
5. Never break builds on API failure
See `references/component-patterns.md` for the Steam API pattern example.
## Phase 11: Deployment
Read `references/deployment-cloudflare.md` for gotchas.
1. Clean build: `bun run build` (never `bun build`)
2. Local test: `bun preview` (port 8788)
3. Set secrets: `wrangler secret put RESEND_API_KEY`
4. Set env: `CLOUDFLARE_ACCOUNT_ID=<id>`
5. Deploy: `bun run deploy`
6. Custom domain + rate limiting in Cloudflare dashboard
## Phase 12: Generate CLAUDE.md
Generate a comprehensive CLAUDE.md documenting the entire project:
- Description, stack, commands
- Architecture (rendering, routing, hydration, content collections)
- Component inventory with hydration strategy
- Design system tokens
- SEO setup (structured data, meta, sitemap, RSS, LLM files)
- Contact form system
- Deployment notes and gotchas
- Key files list and URL patterns

View File

@ -0,0 +1,24 @@
# Example Asset File
This placeholder represents where asset files would be stored.
Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed.
Asset files are NOT intended to be loaded into context, but rather used within
the output Claude produces.
Example asset files from other skills:
- Brand guidelines: logo.png, slides_template.pptx
- Frontend builder: hello-world/ directory with HTML/React boilerplate
- Typography: custom-font.ttf, font-family.woff2
- Data: sample_data.csv, test_dataset.json
## Common Asset Types
- Templates: .pptx, .docx, boilerplate directories
- Images: .png, .jpg, .svg, .gif
- Fonts: .ttf, .otf, .woff, .woff2
- Boilerplate code: Project directories, starter files
- Icons: .ico, .svg
- Data files: .csv, .json, .xml, .yaml
Note: This is a text placeholder. Actual assets can be any file type.

View File

@ -0,0 +1,34 @@
# Reference Documentation for Astro Portfolio Site
This is a placeholder for detailed reference documentation.
Replace with actual reference content or delete if not needed.
Example real reference docs from other skills:
- product-management/references/communication.md - Comprehensive guide for status updates
- product-management/references/context_building.md - Deep-dive on gathering context
- bigquery/references/ - API references and query examples
## When Reference Docs Are Useful
Reference docs are ideal for:
- Comprehensive API documentation
- Detailed workflow guides
- Complex multi-step processes
- Information too lengthy for main SKILL.md
- Content that's only needed for specific use cases
## Structure Suggestions
### API Reference Example
- Overview
- Authentication
- Endpoints with examples
- Error codes
- Rate limits
### Workflow Guide Example
- Prerequisites
- Step-by-step instructions
- Common patterns
- Troubleshooting
- Best practices

View File

@ -0,0 +1,273 @@
# Design System
Complete `global.css` structure for the design system. Adapt colors, fonts, and effects to match the client's brand.
## File Structure
The CSS file follows this exact order:
1. Tailwind import
2. Font-face declarations
3. Tailwind variants and plugins
4. @theme block (design tokens)
5. :root variables (shadcn compatibility, optional)
6. Global element styles
7. Custom cursor (optional)
8. Scrollbar theming
9. Utility classes
10. Keyframe animations
11. Reduced motion resets
12. Safe viewport utilities
13. @theme inline block (shadcn, optional)
14. Dark mode overrides (optional)
## Complete Template
```css
@import "tailwindcss";
/* ── Custom Font ── */
@font-face {
font-family: "{{DISPLAY_FONT_NAME}}";
src: url("/assets/fonts/{{FONT_FILE}}.woff2") format("woff2"),
url("/assets/fonts/{{FONT_FILE}}.ttf") format("truetype");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@custom-variant dark (&:is(.dark *));
@plugin "@tailwindcss/typography";
/* ── Design Tokens ── */
@theme {
/* Brand colors — substitute from client brief */
--color-primary: {{PRIMARY_COLOR}}; /* e.g. #FF006E */
--color-primary-light: {{PRIMARY_LIGHT}}; /* lighter variant */
--color-primary-dark: {{PRIMARY_DARK}}; /* darker variant */
--color-accent: {{ACCENT_COLOR}}; /* e.g. #7B61FF */
--color-secondary: {{SECONDARY_COLOR}}; /* e.g. #00F0FF */
--color-highlight: {{HIGHLIGHT_COLOR}}; /* e.g. #FFE600 */
--color-dark: #0A0A0A;
--color-darker: #050505;
--color-gray: #1A1A1A;
/* Font stacks */
--font-display: "{{DISPLAY_FONT}}", {{DISPLAY_FALLBACK}};
--font-body: "{{BODY_FONT}}", sans-serif;
--font-mono: "{{MONO_FONT}}", monospace;
/* Pixel shadows (4px offset = retro pixel aesthetic) */
--shadow-pixel-primary: 4px 4px 0 0 {{PRIMARY_DARK}};
--shadow-pixel-primary-lg: 8px 8px 0 0 {{PRIMARY_DARK}};
--shadow-pixel-accent: 4px 4px 0 0 {{ACCENT_DARK}};
/* Glow effects */
--shadow-glow-primary: 0 0 20px {{PRIMARY_COLOR}}, 0 0 40px {{PRIMARY_COLOR}};
--shadow-glow-accent: 0 0 20px {{ACCENT_COLOR}}, 0 0 40px {{ACCENT_COLOR}};
--shadow-glow-secondary: 0 0 20px {{SECONDARY_COLOR}}, 0 0 40px {{SECONDARY_COLOR}};
}
/* ── Global Styles ── */
:root {
--cursor-size: 20px;
--radius: 0px; /* sharp pixel aesthetic — set to 0.5rem for rounded */
}
html {
cursor: none; /* hide for custom cursor — remove if not using custom cursor */
}
@media (pointer: coarse) {
html { cursor: auto; }
}
body {
font-family: var(--font-body);
background-color: var(--color-dark);
color: #ffffff;
margin: 0;
padding: 0;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-display);
text-transform: uppercase; /* remove if display font is not pixel/block style */
}
code, pre {
font-family: var(--font-mono);
}
/* ── Custom Cursor (optional — remove block if not using) ── */
#custom-cursor {
position: fixed;
top: 0;
left: 0;
width: var(--cursor-size);
height: var(--cursor-size);
border: 2px solid var(--color-primary);
pointer-events: none;
z-index: 9999;
transform: translate(-50%, -50%);
mix-blend-mode: difference;
transition: width 0.2s, height 0.2s, background-color 0.2s;
}
#custom-cursor::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 4px;
height: 4px;
background-color: var(--color-secondary);
transform: translate(-50%, -50%);
}
@media (pointer: coarse) {
#custom-cursor { display: none; }
}
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: var(--color-darker); }
::-webkit-scrollbar-thumb {
background: var(--color-primary-dark);
border: 2px solid var(--color-darker);
}
::-webkit-scrollbar-thumb:hover { background: var(--color-primary); }
/* ── Utility Classes ── */
.text-glow-primary { text-shadow: 0 0 10px var(--color-primary); }
.text-glow-primary-hover {
transition: text-shadow 0.3s ease, color 0.3s ease;
}
.text-glow-primary-hover:hover {
color: var(--color-primary);
text-shadow: 0 0 10px var(--color-primary);
}
.text-glow-accent { text-shadow: 0 0 10px var(--color-accent); }
.text-glow-secondary { text-shadow: 0 0 10px var(--color-secondary); }
.box-pixel { box-shadow: 4px 4px 0 0 var(--color-primary-dark); }
.box-pixel-accent { box-shadow: 4px 4px 0 0 {{ACCENT_DARK}}; }
.box-pixel-secondary { box-shadow: 4px 4px 0 0 {{SECONDARY_DARK}}; }
.pixel-art {
image-rendering: pixelated;
image-rendering: crisp-edges;
}
.scrollbar-none {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-none::-webkit-scrollbar { display: none; }
/* ── Atmospheric Overlays ── */
.scanlines { position: relative; }
.scanlines::before {
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.15) 2px,
rgba(0, 0, 0, 0.15) 4px
);
pointer-events: none;
z-index: 10;
}
.crt-screen { position: relative; overflow: hidden; }
.crt-screen::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%);
background-size: 100% 4px;
pointer-events: none;
z-index: 10;
}
.crt-screen::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, transparent 0%, rgba(0, 0, 0, 0.4) 100%);
pointer-events: none;
z-index: 11;
}
/* ── Keyframe Animations ── */
@keyframes vhs-flicker {
0%, 100% { opacity: 1; }
92% { opacity: 1; }
93% { opacity: 0.8; }
94% { opacity: 1; }
96% { opacity: 0.9; }
97% { opacity: 1; }
}
.vhs-flicker { animation: vhs-flicker 4s infinite; }
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.animate-blink { animation: blink 1s infinite; }
@keyframes glitch {
0% { transform: translate(0); }
20% { transform: translate(-2px, 2px); }
40% { transform: translate(-2px, -2px); }
60% { transform: translate(2px, 2px); }
80% { transform: translate(2px, -2px); }
100% { transform: translate(0); }
}
.animate-glitch { animation: glitch 0.3s cubic-bezier(.25,.46,.45,.94) both infinite; }
@keyframes marquee-scroll {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
.marquee-track { animation: marquee-scroll 20s linear infinite; }
/* ── Reduced Motion ── */
@media (prefers-reduced-motion: reduce) {
.marquee-track { animation: none; }
.vhs-flicker { animation: none; }
.animate-blink { animation: none; }
.animate-glitch { animation: none; }
}
/* ── Safe Viewport Height ── */
.h-screen-safe { height: 100vh; height: 100dvh; }
.min-h-screen-safe { min-height: 100vh; min-height: 100dvh; }
```
## Color Token Mapping Guide
Map any brand palette to the token system:
| Token | Purpose | Example |
|-------|---------|---------|
| `--color-primary` | Main brand color, CTAs, links, highlights | #FF006E |
| `--color-primary-light` | Hover states, lighter accents | Lighten primary 20% |
| `--color-primary-dark` | Shadows, pressed states, dark accents | Darken primary 20% |
| `--color-accent` | Secondary brand color, headings, tags | #7B61FF |
| `--color-secondary` | Tertiary color, links, code text | #00F0FF |
| `--color-highlight` | Warnings, special callouts | #FFE600 |
| `--color-dark` | Page backgrounds | #0A0A0A |
| `--color-darker` | Footer, deeper sections | #050505 |
| `--color-gray` | Card backgrounds, borders | #1A1A1A |
## Font Strategy
- **Self-host** pixel/display fonts as woff2 + ttf — preload the woff2 in BaseHead
- **Google Fonts** for body + mono fonts — preconnect in BaseHead
- Always use `font-display: swap` to prevent FOUT blocking
- Preload pattern in BaseHead: `<link rel="preload" href="/assets/fonts/Font.woff2" as="font" type="font/woff2" crossorigin />`

View File

@ -0,0 +1,190 @@
# Stack Configuration
## astro.config.mjs
```javascript
// @ts-check
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
import tailwindcss from '@tailwindcss/vite';
import react from '@astrojs/react';
export default defineConfig({
site: '{{SITE_URL}}', // e.g. 'https://example.com'
integrations: [
mdx(),
sitemap({
filter: (page) =>
!['/404', '/privacy', '/terms', '/shop'].some((p) => page.includes(p)),
}),
react(),
],
adapter: cloudflare({
platformProxy: { enabled: true },
imageService: "compile"
}),
vite: {
plugins: [tailwindcss()],
},
});
```
Key notes:
- `site` is used for canonical URLs, OG tags, sitemap, RSS
- Sitemap filter excludes legal, 404, and shop pages
- `platformProxy: { enabled: true }` gives access to Cloudflare bindings in dev
- `imageService: "compile"` uses Astro's built-in image optimization
- No separate tailwind.config or postcss.config — Tailwind 4 uses Vite plugin
## tsconfig.json
```json
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"strictNullChecks": true,
"types": ["./worker-configuration.d.ts", "node"],
"baseUrl": ".",
"paths": { "@/*": ["./src/*"] },
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}
```
## wrangler.jsonc
```jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "{{PROJECT_NAME}}",
"compatibility_date": "2025-12-05",
"compatibility_flags": ["nodejs_compat"],
"pages_build_output_dir": "./dist",
"observability": { "enabled": true },
"vars": {
"CONTACT_EMAIL": "{{CONTACT_EMAIL}}"
}
}
```
**Critical**: Pages does NOT support `account_id` or `ratelimits` in config. Wrangler will reject the deploy. Set account ID via env var, configure rate limiting in dashboard.
## package.json scripts
```json
{
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro build && wrangler pages dev",
"astro": "astro",
"deploy": "astro build && wrangler pages deploy",
"cf-typegen": "wrangler types",
"convert:avif": "node src/utils/convert-to-avif.js",
"convert:avif:all": "node src/utils/convert-to-avif.js --all",
"convert:avif:jpeg": "node src/utils/convert-to-avif.js --jpeg",
"convert:avif:png": "node src/utils/convert-to-avif.js --png",
"commit": "node src/utils/git-commit.js"
}
}
```
**Critical**: `bun build` invokes bun's bundler, not astro's. Always use `bun run build`.
## Dependencies
```json
{
"dependencies": {
"@astrojs/cloudflare": "^12.6.12",
"@astrojs/mdx": "^4.3.12",
"@astrojs/react": "^4.4.2",
"@astrojs/rss": "^4.0.14",
"@astrojs/sitemap": "^3.6.0",
"@react-email/components": "^1.0.8",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.17",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"astro": "^5.16.4",
"clsx": "^2.1.1",
"fuse.js": "^7.1.0",
"gsap": "^3.14.2",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"react-icons": "^5.5.0",
"resend": "^6.9.2",
"sharp": "^0.34.3",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.17"
},
"devDependencies": {
"@types/node": "^24.10.1",
"wrangler": "^4.53.0"
}
}
```
## src/env.d.ts
```typescript
type Runtime = import("@astrojs/cloudflare").Runtime<Env>;
declare namespace App {
interface Locals extends Runtime {}
}
interface Env {
RESEND_API_KEY: string;
CONTACT_RATE_LIMITER: RateLimit;
}
```
## src/consts.ts
```typescript
export const SITE_TITLE = '{{COMPANY_NAME}} — {{TAGLINE}}';
export const SITE_DESCRIPTION = '{{SEO_DESCRIPTION}}';
export const HTML_MARKER = "Built by {{COMPANY_NAME}}";
export const SOCIAL_LINKS = {
email: '{{CONTACT_EMAIL}}',
website: '{{SITE_URL}}',
// Include only the ones the client has:
// steam: 'https://store.steampowered.com/...',
// twitter: 'https://twitter.com/...',
// discord: 'https://discord.gg/...',
// instagram: 'https://instagram.com/...',
// github: 'https://github.com/...',
};
```
## src/lib/utils.ts
```typescript
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
## src/utils/reading-time.ts
```typescript
export function calculateReadingTime(content: string | undefined, wordsPerMinute: number = 200): string {
const wordCount = content?.split(/\s+/).length || 0;
const readingTime = Math.max(1, Math.ceil(wordCount / wordsPerMinute));
return `${readingTime} min read`;
}
```
## worker-configuration.d.ts
Generated by `bun cf-typegen`. Run this after initial setup to generate Cloudflare Worker runtime types.

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
"""
Example helper script for astro-portfolio-site
This is a placeholder script that can be executed directly.
Replace with actual implementation or delete if not needed.
Example real scripts from other skills:
- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields
- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images
"""
def main():
print("This is an example script for astro-portfolio-site")
# TODO: Add actual script logic here
# This could be data processing, file conversion, API calls, etc.
if __name__ == "__main__":
main()