Compare commits

...

7 Commits

Author SHA1 Message Date
zguiyang
123b3edc64 refactor(blog): restore two-column index layout with sidebar 2026-03-16 22:30:33 +08:00
zguiyang
f76a00c0a6 feat(projects): simplify categories and add cover-ready card layout 2026-03-16 22:30:26 +08:00
zguiyang
d873c3b063 fix(nav): keep active menu state synced during client-side navigation 2026-03-16 22:16:16 +08:00
zguiyang
bafd029b95 feat(site): refocus portfolio for personal brand and AI full-stack profile 2026-03-16 22:12:53 +08:00
zguiyang
2634b7a95b feat: update public contact info and social visibility 2026-03-16 21:18:01 +08:00
zguiyang
411bbb00d1 feat(site): optimize full-stack positioning and collaboration conversion pages 2026-03-16 16:18:28 +08:00
zguiyang
ce6110588f feat(contact): add multilingual contact pages and dynamic configurations
- Created `src/pages/contact.astro` and `src/pages/zh/contact.astro` to support multilingual contact functionality.
- Introduced `contactIntents` and `contactMethods` dynamic configurations in `src/lib/data/contact.ts` for streamlined content management.
- Designed responsive and localized layouts with enhanced UX for both languages.
2026-03-16 15:32:40 +08:00
47 changed files with 4287 additions and 2663 deletions

View File

@@ -0,0 +1,156 @@
---
name: openspec-apply-change
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Implement tasks from an OpenSpec change.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context if the user mentioned a change
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`).
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- Context file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
4. **Read context files**
Read the files listed in `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! Ready to archive this change.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly

View File

@@ -0,0 +1,114 @@
---
name: openspec-archive-change
description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Archive a completed change in the experimental workflow.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show only active changes (not already archived).
Include the schema used for each change if available.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check artifact completion status**
Run `openspec status --change "<name>" --json` to check artifact completion.
Parse the JSON to understand:
- `schemaName`: The workflow being used
- `artifacts`: List of artifacts with their status (`done` or other)
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create the archive directory if it doesn't exist:
```bash
mkdir -p openspec/changes/archive
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move the change directory to archive
```bash
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Whether specs were synced (if applicable)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")
All artifacts complete. All tasks complete.
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use openspec-sync-specs approach (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting

View File

@@ -0,0 +1,246 @@
---
name: openspec-bulk-archive-change
description: Archive multiple completed changes at once. Use when archiving several parallel changes.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Archive multiple completed changes in a single operation.
This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.
**Input**: None required (prompts for selection)
**Steps**
1. **Get active changes**
Run `openspec list --json` to get all active changes.
If no active changes exist, inform user and stop.
2. **Prompt for change selection**
Use **AskUserQuestion tool** with multi-select to let user choose changes:
- Show each change with its schema
- Include an option for "All changes"
- Allow any number of selections (1+ works, 2+ is the typical use case)
**IMPORTANT**: Do NOT auto-select. Always let the user choose.
3. **Batch validation - gather status for all selected changes**
For each selected change, collect:
a. **Artifact status** - Run `openspec status --change "<name>" --json`
- Parse `schemaName` and `artifacts` list
- Note which artifacts are `done` vs other states
b. **Task completion** - Read `openspec/changes/<name>/tasks.md`
- Count `- [ ]` (incomplete) vs `- [x]` (complete)
- If no tasks file exists, note as "No tasks"
c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory
- List which capability specs exist
- For each, extract requirement names (lines matching `### Requirement: <name>`)
4. **Detect spec conflicts**
Build a map of `capability -> [changes that touch it]`:
```
auth -> [change-a, change-b] <- CONFLICT (2+ changes)
api -> [change-c] <- OK (only 1 change)
```
A conflict exists when 2+ selected changes have delta specs for the same capability.
5. **Resolve conflicts agentically**
**For each conflict**, investigate the codebase:
a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify
b. **Search the codebase** for implementation evidence:
- Look for code implementing requirements from each delta spec
- Check for related files, functions, or tests
c. **Determine resolution**:
- If only one change is actually implemented -> sync that one's specs
- If both implemented -> apply in chronological order (older first, newer overwrites)
- If neither implemented -> skip spec sync, warn user
d. **Record resolution** for each conflict:
- Which change's specs to apply
- In what order (if both)
- Rationale (what was found in codebase)
6. **Show consolidated status table**
Display a table summarizing all changes:
```
| Change | Artifacts | Tasks | Specs | Conflicts | Status |
|---------------------|-----------|-------|---------|-----------|--------|
| schema-management | Done | 5/5 | 2 delta | None | Ready |
| project-config | Done | 3/3 | 1 delta | None | Ready |
| add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* |
| add-verify-skill | 1 left | 2/5 | None | None | Warn |
```
For conflicts, show the resolution:
```
* Conflict resolution:
- auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)
```
For incomplete changes, show warnings:
```
Warnings:
- add-verify-skill: 1 incomplete artifact, 3 incomplete tasks
```
7. **Confirm batch operation**
Use **AskUserQuestion tool** with a single confirmation:
- "Archive N changes?" with options based on status
- Options might include:
- "Archive all N changes"
- "Archive only N ready changes (skip incomplete)"
- "Cancel"
If there are incomplete changes, make clear they'll be archived with warnings.
8. **Execute archive for each confirmed change**
Process changes in the determined order (respecting conflict resolution):
a. **Sync specs** if delta specs exist:
- Use the openspec-sync-specs approach (agent-driven intelligent merge)
- For conflicts, apply in resolved order
- Track if sync was done
b. **Perform the archive**:
```bash
mkdir -p openspec/changes/archive
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
```
c. **Track outcome** for each change:
- Success: archived successfully
- Failed: error during archive (record error)
- Skipped: user chose not to archive (if applicable)
9. **Display summary**
Show final results:
```
## Bulk Archive Complete
Archived 3 changes:
- schema-management-cli -> archive/2026-01-19-schema-management-cli/
- project-config -> archive/2026-01-19-project-config/
- add-oauth -> archive/2026-01-19-add-oauth/
Skipped 1 change:
- add-verify-skill (user chose not to archive incomplete)
Spec sync summary:
- 4 delta specs synced to main specs
- 1 conflict resolved (auth: applied both in chronological order)
```
If any failures:
```
Failed 1 change:
- some-change: Archive directory already exists
```
**Conflict Resolution Examples**
Example 1: Only one implemented
```
Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]
Checking add-oauth:
- Delta adds "OAuth Provider Integration" requirement
- Searching codebase... found src/auth/oauth.ts implementing OAuth flow
Checking add-jwt:
- Delta adds "JWT Token Handling" requirement
- Searching codebase... no JWT implementation found
Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.
```
Example 2: Both implemented
```
Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]
Checking add-rest-api (created 2026-01-10):
- Delta adds "REST Endpoints" requirement
- Searching codebase... found src/api/rest.ts
Checking add-graphql (created 2026-01-15):
- Delta adds "GraphQL Schema" requirement
- Searching codebase... found src/api/graphql.ts
Resolution: Both implemented. Will apply add-rest-api specs first,
then add-graphql specs (chronological order, newer takes precedence).
```
**Output On Success**
```
## Bulk Archive Complete
Archived N changes:
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
- <change-2> -> archive/YYYY-MM-DD-<change-2>/
Spec sync summary:
- N delta specs synced to main specs
- No conflicts (or: M conflicts resolved)
```
**Output On Partial Success**
```
## Bulk Archive Complete (partial)
Archived N changes:
- <change-1> -> archive/YYYY-MM-DD-<change-1>/
Skipped M changes:
- <change-2> (user chose not to archive incomplete)
Failed K changes:
- <change-3>: Archive directory already exists
```
**Output When No Changes**
```
## No Changes to Archive
No active changes found. Create a new change to get started.
```
**Guardrails**
- Allow any number of changes (1+ is fine, 2+ is the typical use case)
- Always prompt for selection, never auto-select
- Detect spec conflicts early and resolve by checking codebase
- When both changes are implemented, apply specs in chronological order
- Skip spec sync only when implementation is missing (warn user)
- Show clear per-change status before confirming
- Use single confirmation for entire batch
- Track and report all outcomes (success/skip/fail)
- Preserve .openspec.yaml when moving to archive
- Archive directory target uses current date: YYYY-MM-DD-<name>
- If archive target exists, fail that change but continue with others

View File

@@ -0,0 +1,118 @@
---
name: openspec-continue-change
description: Continue working on an OpenSpec change by creating the next artifact. Use when the user wants to progress their change, create the next artifact, or continue their workflow.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Continue working on a change by creating the next artifact.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to work on.
Present the top 3-4 most recently modified changes as options, showing:
- Change name
- Schema (from `schema` field if present, otherwise "spec-driven")
- Status (e.g., "0/5 tasks", "complete", "no tasks")
- How recently it was modified (from `lastModified` field)
Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to continue.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check current status**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand current state. The response includes:
- `schemaName`: The workflow schema being used (e.g., "spec-driven")
- `artifacts`: Array of artifacts with their status ("done", "ready", "blocked")
- `isComplete`: Boolean indicating if all artifacts are complete
3. **Act based on status**:
---
**If all artifacts are complete (`isComplete: true`)**:
- Congratulate the user
- Show final status including the schema used
- Suggest: "All artifacts created! You can now implement this change or archive it."
- STOP
---
**If artifacts are ready to create** (status shows artifacts with `status: "ready"`):
- Pick the FIRST artifact with `status: "ready"` from the status output
- Get its instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- Parse the JSON. The key fields are:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance
- `outputPath`: Where to write the artifact
- `dependencies`: Completed artifacts to read for context
- **Create the artifact file**:
- Read any completed dependency files for context
- Use `template` as the structure - fill in its sections
- Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file
- Write to the output path specified in instructions
- Show what was created and what's now unlocked
- STOP after creating ONE artifact
---
**If no artifacts are ready (all blocked)**:
- This shouldn't happen with a valid schema
- Show status and suggest checking for issues
4. **After creating an artifact, show progress**
```bash
openspec status --change "<name>"
```
**Output**
After each invocation, show:
- Which artifact was created
- Schema workflow being used
- Current progress (N/M complete)
- What artifacts are now unlocked
- Prompt: "Want to continue? Just ask me to continue or tell me what to do next."
**Artifact Creation Guidelines**
The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create.
Common artifact patterns:
**spec-driven schema** (proposal → specs → design → tasks):
- **proposal.md**: Ask user about the change if not clear. Fill in Why, What Changes, Capabilities, Impact.
- The Capabilities section is critical - each capability listed will need a spec file.
- **specs/<capability>/spec.md**: Create one spec per capability listed in the proposal's Capabilities section (use the capability name, not the change name).
- **design.md**: Document technical decisions, architecture, and implementation approach.
- **tasks.md**: Break down implementation into checkboxed tasks.
For other schemas, follow the `instruction` field from the CLI output.
**Guardrails**
- Create ONE artifact per invocation
- Always read dependency artifacts before creating a new one
- Never skip artifacts or create out of order
- If context is unclear, ask the user before creating
- Verify the artifact file exists after writing before marking progress
- Use the schema's artifact sequence, don't assume specific artifact names
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output

View File

@@ -0,0 +1,288 @@
---
name: openspec-explore
description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Read existing artifacts for context**
- `openspec/changes/<name>/proposal.md`
- `openspec/changes/<name>/design.md`
- `openspec/changes/<name>/tasks.md`
- etc.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|--------------|------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Handling Different Entry Points
**User brings a vague idea:**
```
User: I'm thinking about adding real-time collaboration
You: Real-time collab is a big space. Let me think about this...
COLLABORATION SPECTRUM
════════════════════════════════════════════
Awareness Coordination Sync
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Presence│ │Cursors │ │ CRDT │
│ "3 │ │ Multi │ │Conflict│
│online" │ │ select │ │ free │
└────────┘ └────────┘ └────────┘
│ │ │
trivial moderate complex
Where's your head at?
```
**User brings a specific problem:**
```
User: The auth system is a mess
You: [reads codebase]
┌─────────────────────────────────────────────┐
│ CURRENT AUTH FLOW │
└─────────────────────────────────────────────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Google │ │ GitHub │ │ Email │
│ OAuth │ │ OAuth │ │ Magic │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└─────────────┼─────────────┘
┌───────────┐
│ Session │
└─────┬─────┘
┌───────────┐
│ Perms │
└───────────┘
I see three tangles. Which one's burning?
```
**User is stuck mid-implementation:**
```
User: /opsx:explore add-auth-system
The OAuth integration is more complex than expected
You: [reads change artifacts]
You're on task 4: "Implement OAuth flow"
Let me trace what's involved...
[draws diagram, explores options, suggests paths]
Want to update the design to reflect this?
Or add a spike task to investigate?
```
**User wants to compare options:**
```
User: Should we use Postgres or SQLite?
You: Generic answer is boring. What's the context?
User: A CLI tool that tracks local dev environments
You: That changes everything.
┌─────────────────────────────────────────────────┐
│ CLI TOOL DATA STORAGE │
└─────────────────────────────────────────────────┘
Key constraints:
• No daemon running
• Must work offline
• Single user
SQLite Postgres
Deployment embedded ✓ needs server ✗
Offline yes ✓ no ✗
Single file yes ✓ no ✗
SQLite. Not even close.
Unless... is there a sync component?
```
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When it feels like things are crystallizing, you might summarize:
```
## What We Figured Out
**The problem**: [crystallized understanding]
**The approach**: [if one emerged]
**Open questions**: [if any remain]
**Next steps** (if ready):
- Create a change proposal
- Keep exploring: just keep talking
```
But this summary is optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own

View File

@@ -0,0 +1,101 @@
---
name: openspec-ff-change
description: Fast-forward through OpenSpec artifact creation. Use when the user wants to quickly create all artifacts needed for implementation without stepping through each one individually.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Fast-forward through artifact creation - generate everything needed to start implementation in one go.
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
**Steps**
1. **If no clear input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change at `openspec/changes/<name>/`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `outputPath`: Where to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "✓ Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, suggest continuing that change instead
- Verify each artifact file exists after writing before proceeding to next

View File

@@ -0,0 +1,74 @@
---
name: openspec-new-change
description: Start a new OpenSpec change using the experimental artifact workflow. Use when the user wants to create a new feature, fix, or modification with a structured step-by-step approach.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Start a new change using the experimental artifact-driven approach.
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
**Steps**
1. **If no clear input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Determine the workflow schema**
Use the default schema (omit `--schema`) unless the user explicitly requests a different workflow.
**Use a different schema only if the user mentions:**
- A specific schema name → use `--schema <name>`
- "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose
**Otherwise**: Omit `--schema` to use the default.
3. **Create the change directory**
```bash
openspec new change "<name>"
```
Add `--schema <name>` only if the user requested a specific workflow.
This creates a scaffolded change at `openspec/changes/<name>/` with the selected schema.
4. **Show the artifact status**
```bash
openspec status --change "<name>"
```
This shows which artifacts need to be created and which are ready (dependencies satisfied).
5. **Get instructions for the first artifact**
The first artifact depends on the schema (e.g., `proposal` for spec-driven).
Check the status output to find the first artifact with status "ready".
```bash
openspec instructions <first-artifact-id> --change "<name>"
```
This outputs the template and context for creating the first artifact.
6. **STOP and wait for user direction**
**Output**
After completing the steps, summarize:
- Change name and location
- Schema/workflow being used and its artifact sequence
- Current status (0/N artifacts complete)
- The template for the first artifact
- Prompt: "Ready to create the first artifact? Just describe what this change is about and I'll draft it, or ask me to continue."
**Guardrails**
- Do NOT create any artifacts yet - just show the instructions
- Do NOT advance beyond showing the first artifact template
- If the name is invalid (not kebab-case), ask for a valid name
- If a change with that name already exists, suggest continuing that change instead
- Pass --schema if using a non-default workflow

View File

@@ -0,0 +1,554 @@
---
name: openspec-onboard
description: Guided onboarding for OpenSpec - walk through a complete workflow cycle with narration and real codebase work.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.
---
## Preflight
Before starting, check if the OpenSpec CLI is installed:
```bash
# Unix/macOS
openspec --version 2>&1 || echo "CLI_NOT_INSTALLED"
# Windows (PowerShell)
# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" }
```
**If CLI not installed:**
> OpenSpec CLI is not installed. Install it first, then come back to `/opsx:onboard`.
Stop here if not installed.
---
## Phase 1: Welcome
Display:
```
## Welcome to OpenSpec!
I'll walk you through a complete change cycle—from idea to implementation—using a real task in your codebase. Along the way, you'll learn the workflow by doing it.
**What we'll do:**
1. Pick a small, real task in your codebase
2. Explore the problem briefly
3. Create a change (the container for our work)
4. Build the artifacts: proposal → specs → design → tasks
5. Implement the tasks
6. Archive the completed change
**Time:** ~15-20 minutes
Let's start by finding something to work on.
```
---
## Phase 2: Task Selection
### Codebase Analysis
Scan the codebase for small improvement opportunities. Look for:
1. **TODO/FIXME comments** - Search for `TODO`, `FIXME`, `HACK`, `XXX` in code files
2. **Missing error handling** - `catch` blocks that swallow errors, risky operations without try-catch
3. **Functions without tests** - Cross-reference `src/` with test directories
4. **Type issues** - `any` types in TypeScript files (`: any`, `as any`)
5. **Debug artifacts** - `console.log`, `console.debug`, `debugger` statements in non-debug code
6. **Missing validation** - User input handlers without validation
Also check recent git activity:
```bash
# Unix/macOS
git log --oneline -10 2>/dev/null || echo "No git history"
# Windows (PowerShell)
# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" }
```
### Present Suggestions
From your analysis, present 3-4 specific suggestions:
```
## Task Suggestions
Based on scanning your codebase, here are some good starter tasks:
**1. [Most promising task]**
Location: `src/path/to/file.ts:42`
Scope: ~1-2 files, ~20-30 lines
Why it's good: [brief reason]
**2. [Second task]**
Location: `src/another/file.ts`
Scope: ~1 file, ~15 lines
Why it's good: [brief reason]
**3. [Third task]**
Location: [location]
Scope: [estimate]
Why it's good: [brief reason]
**4. Something else?**
Tell me what you'd like to work on.
Which task interests you? (Pick a number or describe your own)
```
**If nothing found:** Fall back to asking what the user wants to build:
> I didn't find obvious quick wins in your codebase. What's something small you've been meaning to add or fix?
### Scope Guardrail
If the user picks or describes something too large (major feature, multi-day work):
```
That's a valuable task, but it's probably larger than ideal for your first OpenSpec run-through.
For learning the workflow, smaller is better—it lets you see the full cycle without getting stuck in implementation details.
**Options:**
1. **Slice it smaller** - What's the smallest useful piece of [their task]? Maybe just [specific slice]?
2. **Pick something else** - One of the other suggestions, or a different small task?
3. **Do it anyway** - If you really want to tackle this, we can. Just know it'll take longer.
What would you prefer?
```
Let the user override if they insist—this is a soft guardrail.
---
## Phase 3: Explore Demo
Once a task is selected, briefly demonstrate explore mode:
```
Before we create a change, let me quickly show you **explore mode**—it's how you think through problems before committing to a direction.
```
Spend 1-2 minutes investigating the relevant code:
- Read the file(s) involved
- Draw a quick ASCII diagram if it helps
- Note any considerations
```
## Quick Exploration
[Your brief analysis—what you found, any considerations]
┌─────────────────────────────────────────┐
│ [Optional: ASCII diagram if helpful] │
└─────────────────────────────────────────┘
Explore mode (`/opsx:explore`) is for this kind of thinking—investigating before implementing. You can use it anytime you need to think through a problem.
Now let's create a change to hold our work.
```
**PAUSE** - Wait for user acknowledgment before proceeding.
---
## Phase 4: Create the Change
**EXPLAIN:**
```
## Creating a Change
A "change" in OpenSpec is a container for all the thinking and planning around a piece of work. It lives in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks.
Let me create one for our task.
```
**DO:** Create the change with a derived kebab-case name:
```bash
openspec new change "<derived-name>"
```
**SHOW:**
```
Created: `openspec/changes/<name>/`
The folder structure:
```
openspec/changes/<name>/
├── proposal.md ← Why we're doing this (empty, we'll fill it)
├── design.md ← How we'll build it (empty)
├── specs/ ← Detailed requirements (empty)
└── tasks.md ← Implementation checklist (empty)
```
Now let's fill in the first artifact—the proposal.
```
---
## Phase 5: Proposal
**EXPLAIN:**
```
## The Proposal
The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work.
I'll draft one based on our task.
```
**DO:** Draft the proposal content (don't save yet):
```
Here's a draft proposal:
---
## Why
[1-2 sentences explaining the problem/opportunity]
## What Changes
[Bullet points of what will be different]
## Capabilities
### New Capabilities
- `<capability-name>`: [brief description]
### Modified Capabilities
<!-- If modifying existing behavior -->
## Impact
- `src/path/to/file.ts`: [what changes]
- [other files if applicable]
---
Does this capture the intent? I can adjust before we save it.
```
**PAUSE** - Wait for user approval/feedback.
After approval, save the proposal:
```bash
openspec instructions proposal --change "<name>" --json
```
Then write the content to `openspec/changes/<name>/proposal.md`.
```
Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves.
Next up: specs.
```
---
## Phase 6: Specs
**EXPLAIN:**
```
## Specs
Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear.
For a small task like this, we might only need one spec file.
```
**DO:** Create the spec file:
```bash
# Unix/macOS
mkdir -p openspec/changes/<name>/specs/<capability-name>
# Windows (PowerShell)
# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>"
```
Draft the spec content:
```
Here's the spec:
---
## ADDED Requirements
### Requirement: <Name>
<Description of what the system should do>
#### Scenario: <Scenario name>
- **WHEN** <trigger condition>
- **THEN** <expected outcome>
- **AND** <additional outcome if needed>
---
This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases.
```
Save to `openspec/changes/<name>/specs/<capability>/spec.md`.
---
## Phase 7: Design
**EXPLAIN:**
```
## Design
The design captures **how** we'll build it—technical decisions, tradeoffs, approach.
For small changes, this might be brief. That's fine—not every change needs deep design discussion.
```
**DO:** Draft design.md:
```
Here's the design:
---
## Context
[Brief context about the current state]
## Goals / Non-Goals
**Goals:**
- [What we're trying to achieve]
**Non-Goals:**
- [What's explicitly out of scope]
## Decisions
### Decision 1: [Key decision]
[Explanation of approach and rationale]
---
For a small task, this captures the key decisions without over-engineering.
```
Save to `openspec/changes/<name>/design.md`.
---
## Phase 8: Tasks
**EXPLAIN:**
```
## Tasks
Finally, we break the work into implementation tasks—checkboxes that drive the apply phase.
These should be small, clear, and in logical order.
```
**DO:** Generate tasks based on specs and design:
```
Here are the implementation tasks:
---
## 1. [Category or file]
- [ ] 1.1 [Specific task]
- [ ] 1.2 [Specific task]
## 2. Verify
- [ ] 2.1 [Verification step]
---
Each checkbox becomes a unit of work in the apply phase. Ready to implement?
```
**PAUSE** - Wait for user to confirm they're ready to implement.
Save to `openspec/changes/<name>/tasks.md`.
---
## Phase 9: Apply (Implementation)
**EXPLAIN:**
```
## Implementation
Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach.
```
**DO:** For each task:
1. Announce: "Working on task N: [description]"
2. Implement the change in the codebase
3. Reference specs/design naturally: "The spec says X, so I'm doing Y"
4. Mark complete in tasks.md: `- [ ]``- [x]`
5. Brief status: "✓ Task N complete"
Keep narration light—don't over-explain every line of code.
After all tasks:
```
## Implementation Complete
All tasks done:
- [x] Task 1
- [x] Task 2
- [x] ...
The change is implemented! One more step—let's archive it.
```
---
## Phase 10: Archive
**EXPLAIN:**
```
## Archiving
When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`.
Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way.
```
**DO:**
```bash
openspec archive "<name>"
```
**SHOW:**
```
Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/`
The change is now part of your project's history. The code is in your codebase, the decision record is preserved.
```
---
## Phase 11: Recap & Next Steps
```
## Congratulations!
You just completed a full OpenSpec cycle:
1. **Explore** - Thought through the problem
2. **New** - Created a change container
3. **Proposal** - Captured WHY
4. **Specs** - Defined WHAT in detail
5. **Design** - Decided HOW
6. **Tasks** - Broke it into steps
7. **Apply** - Implemented the work
8. **Archive** - Preserved the record
This same rhythm works for any size change—a small fix or a major feature.
---
## Command Reference
**Core workflow:**
| Command | What it does |
|---------|--------------|
| `/opsx:propose` | Create a change and generate all artifacts |
| `/opsx:explore` | Think through problems before/during work |
| `/opsx:apply` | Implement tasks from a change |
| `/opsx:archive` | Archive a completed change |
**Additional commands:**
| Command | What it does |
|---------|--------------|
| `/opsx:new` | Start a new change, step through artifacts one at a time |
| `/opsx:continue` | Continue working on an existing change |
| `/opsx:ff` | Fast-forward: create all artifacts at once |
| `/opsx:verify` | Verify implementation matches artifacts |
---
## What's Next?
Try `/opsx:propose` on something you actually want to build. You've got the rhythm now!
```
---
## Graceful Exit Handling
### User wants to stop mid-way
If the user says they need to stop, want to pause, or seem disengaged:
```
No problem! Your change is saved at `openspec/changes/<name>/`.
To pick up where we left off later:
- `/opsx:continue <name>` - Resume artifact creation
- `/opsx:apply <name>` - Jump to implementation (if tasks exist)
The work won't be lost. Come back whenever you're ready.
```
Exit gracefully without pressure.
### User just wants command reference
If the user says they just want to see the commands or skip the tutorial:
```
## OpenSpec Quick Reference
**Core workflow:**
| Command | What it does |
|---------|--------------|
| `/opsx:propose <name>` | Create a change and generate all artifacts |
| `/opsx:explore` | Think through problems (no code changes) |
| `/opsx:apply <name>` | Implement tasks |
| `/opsx:archive <name>` | Archive when done |
**Additional commands:**
| Command | What it does |
|---------|--------------|
| `/opsx:new <name>` | Start a new change, step by step |
| `/opsx:continue <name>` | Continue an existing change |
| `/opsx:ff <name>` | Fast-forward: all artifacts at once |
| `/opsx:verify <name>` | Verify implementation |
Try `/opsx:propose` to start your first change.
```
Exit gracefully.
---
## Guardrails
- **Follow the EXPLAIN → DO → SHOW → PAUSE pattern** at key transitions (after explore, after proposal draft, after tasks, after archive)
- **Keep narration light** during implementation—teach without lecturing
- **Don't skip phases** even if the change is small—the goal is teaching the workflow
- **Pause for acknowledgment** at marked points, but don't over-pause
- **Handle exits gracefully**—never pressure the user to continue
- **Use real codebase tasks**—don't simulate or use fake examples
- **Adjust scope gently**—guide toward smaller tasks but respect user choice

View File

@@ -0,0 +1,138 @@
---
name: openspec-sync-specs
description: Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Sync delta specs from a change to main specs.
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show changes that have delta specs (under `specs/` directory).
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Find delta specs**
Look for delta spec files in `openspec/changes/<name>/specs/*/spec.md`.
Each delta spec file contains sections like:
- `## ADDED Requirements` - New requirements to add
- `## MODIFIED Requirements` - Changes to existing requirements
- `## REMOVED Requirements` - Requirements to remove
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
If no delta specs found, inform user and stop.
3. **For each delta spec, apply changes to main specs**
For each capability with a delta spec at `openspec/changes/<name>/specs/<capability>/spec.md`:
a. **Read the delta spec** to understand the intended changes
b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)
c. **Apply changes intelligently**:
**ADDED Requirements:**
- If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:**
- Find the requirement in main spec
- Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios
- Changing the requirement description
- Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:**
- Remove the entire requirement block from main spec
**RENAMED Requirements:**
- Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements
4. **Show summary**
After applying all changes, summarize:
- Which capabilities were updated
- What changes were made (requirements added/modified/removed/renamed)
**Delta Spec Format Reference**
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
## MODIFIED Requirements
### Requirement: Existing Feature
#### Scenario: New scenario to add
- **WHEN** user does A
- **THEN** system does B
## REMOVED Requirements
### Requirement: Deprecated Feature
## RENAMED Requirements
- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`
```
**Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement
- Use your judgment to merge changes sensibly
**Output On Success**
```
## Specs Synced: <change-name>
Updated main specs:
**<capability-1>**:
- Added requirement: "New Feature"
- Modified requirement: "Existing Feature" (added 1 scenario)
**<capability-2>**:
- Created new spec file
- Added requirement: "Another Feature"
Main specs are now updated. The change remains active - archive when implementation is complete.
```
**Guardrails**
- Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification
- Show what you're changing as you go
- The operation should be idempotent - running twice should give same result

View File

@@ -0,0 +1,168 @@
---
name: openspec-verify-change
description: Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
---
Verify that an implementation matches the change artifacts (specs, tasks, design).
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show changes that have implementation tasks (tasks artifact exists).
Include the schema used for each change if available.
Mark changes with incomplete tasks as "(In Progress)".
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- Which artifacts exist for this change
3. **Get the change directory and load artifacts**
```bash
openspec instructions apply --change "<name>" --json
```
This returns the change directory and context files. Read all available artifacts from `contextFiles`.
4. **Initialize verification report structure**
Create a report structure with three dimensions:
- **Completeness**: Track tasks and spec coverage
- **Correctness**: Track requirement implementation and scenario coverage
- **Coherence**: Track design adherence and pattern consistency
Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
5. **Verify Completeness**
**Task Completion**:
- If tasks.md exists in contextFiles, read it
- Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete)
- Count complete vs total tasks
- If incomplete tasks exist:
- Add CRITICAL issue for each incomplete task
- Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
**Spec Coverage**:
- If delta specs exist in `openspec/changes/<name>/specs/`:
- Extract all requirements (marked with "### Requirement:")
- For each requirement:
- Search codebase for keywords related to the requirement
- Assess if implementation likely exists
- If requirements appear unimplemented:
- Add CRITICAL issue: "Requirement not found: <requirement name>"
- Recommendation: "Implement requirement X: <description>"
6. **Verify Correctness**
**Requirement Implementation Mapping**:
- For each requirement from delta specs:
- Search codebase for implementation evidence
- If found, note file paths and line ranges
- Assess if implementation matches requirement intent
- If divergence detected:
- Add WARNING: "Implementation may diverge from spec: <details>"
- Recommendation: "Review <file>:<lines> against requirement X"
**Scenario Coverage**:
- For each scenario in delta specs (marked with "#### Scenario:"):
- Check if conditions are handled in code
- Check if tests exist covering the scenario
- If scenario appears uncovered:
- Add WARNING: "Scenario not covered: <scenario name>"
- Recommendation: "Add test or implementation for scenario: <description>"
7. **Verify Coherence**
**Design Adherence**:
- If design.md exists in contextFiles:
- Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
- Verify implementation follows those decisions
- If contradiction detected:
- Add WARNING: "Design decision not followed: <decision>"
- Recommendation: "Update implementation or revise design.md to match reality"
- If no design.md: Skip design adherence check, note "No design.md to verify against"
**Code Pattern Consistency**:
- Review new code for consistency with project patterns
- Check file naming, directory structure, coding style
- If significant deviations found:
- Add SUGGESTION: "Code pattern deviation: <details>"
- Recommendation: "Consider following project pattern: <example>"
8. **Generate Verification Report**
**Summary Scorecard**:
```
## Verification Report: <change-name>
### Summary
| Dimension | Status |
|--------------|------------------|
| Completeness | X/Y tasks, N reqs|
| Correctness | M/N reqs covered |
| Coherence | Followed/Issues |
```
**Issues by Priority**:
1. **CRITICAL** (Must fix before archive):
- Incomplete tasks
- Missing requirement implementations
- Each with specific, actionable recommendation
2. **WARNING** (Should fix):
- Spec/design divergences
- Missing scenario coverage
- Each with specific recommendation
3. **SUGGESTION** (Nice to fix):
- Pattern inconsistencies
- Minor improvements
- Each with specific recommendation
**Final Assessment**:
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
- If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
- If all clear: "All checks passed. Ready for archive."
**Verification Heuristics**
- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)
- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty
- **Coherence**: Look for glaring inconsistencies, don't nitpick style
- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
**Graceful Degradation**
- If only tasks.md exists: verify task completion only, skip spec/design checks
- If tasks + specs exist: verify completeness and correctness, skip design
- If full artifacts: verify all three dimensions
- Always note which checks were skipped and why
**Output Format**
Use clear markdown with:
- Table for summary scorecard
- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)
- Code references in format: `file.ts:123`
- Specific, actionable recommendations
- No vague suggestions like "consider reviewing"

View File

@@ -9,9 +9,9 @@ export default function AuthorCard({ lang, author }: AuthorCardProps) {
? personalInfo.description.zh
: personalInfo.description.en,
avatar: personalInfo.avatar,
website: personalInfo.website || 'https://zhaoguiyang.com',
website: personalInfo.website,
github: personalInfo.github,
twitter: personalInfo.twitter || 'https://twitter.com/zhaoguiyang',
twitter: personalInfo.twitter,
linkedin: personalInfo.linkedin
};
@@ -79,38 +79,9 @@ export default function AuthorCard({ lang, author }: AuthorCardProps) {
</a>
)}
{authorInfo.twitter && (
<a
href={authorInfo.twitter}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center w-8 h-8 rounded-full bg-muted text-muted-foreground hover:bg-blue-500/10 hover:text-blue-500 transition-all duration-200"
title="Twitter"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
</svg>
</a>
)}
{/* LinkedIn link temporarily hidden
{authorInfo.linkedin && (
<a
href={authorInfo.linkedin}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center w-8 h-8 rounded-full bg-muted text-muted-foreground hover:bg-blue-600/10 hover:text-blue-600 transition-all duration-200"
title="LinkedIn"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
</a>
)}
*/}
</div>
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,45 @@
---
import { contactIntents, contactMethods } from '@/lib/data';
import type { Lang } from '@/types/i18n';
interface Props {
lang: Lang;
showIntents?: boolean;
}
const { lang, showIntents = true } = Astro.props;
const isZh = lang === 'zh';
---
<section class="page-content-main mt-6 page-surface p-8" id="contact-card">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '联系我' : 'Contact'}</h2>
<p class="mt-3 text-sm text-muted-foreground">
{isZh
? '欢迎交流岗位机会、专项协作,或工程实践相关话题。'
: 'Open to role opportunities, scoped collaboration, and engineering discussions.'}
</p>
{showIntents && (
<div class="mt-5 grid gap-4 md:grid-cols-3">
{contactIntents.map((intent) => (
<article class="rounded-md border border-border/70 p-4">
<h3 class="text-sm font-bold">{intent.title[lang]}</h3>
<p class="mt-2 text-sm text-muted-foreground">{intent.description[lang]}</p>
</article>
))}
</div>
)}
<ul class="mt-6 space-y-3 text-sm">
{contactMethods.map((method) => (
<li class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between border-b border-border/70 pb-3 last:border-none last:pb-0">
<span class="font-semibold text-foreground">{method.label[lang]}</span>
{method.href ? (
<a href={method.href} target="_blank" rel="noopener noreferrer" class="text-primary hover:text-primary/80 break-all">{method.value}</a>
) : (
<span class="text-muted-foreground break-all">{method.value}</span>
)}
</li>
))}
</ul>
</section>

View File

@@ -39,7 +39,7 @@ export default function Footer({ lang: propLang }: FooterProps) {
className="text-sm text-primary font-medium text-center md:text-left"
whileHover={{ scale: 1.01 }}
>
{lang === 'zh' ? '构建 AI 产品?寻找技术合伙人?联系我' : 'Building AI Products? Need a Technical Co-founder? Contact me!'}
{lang === 'zh' ? '正在招聘远程工程师或寻求项目合作?欢迎联系我' : 'Hiring for a remote engineering role or looking for project collaboration? Lets connect.'}
</motion.p>
</motion.div>
<motion.p

View File

@@ -5,7 +5,7 @@ import Container from "./ui/Container.tsx";
import { useTranslations, getLocalizedPath } from "@/i18n/utils";
import type { Lang } from "@/types/i18n";
import { useState, useEffect } from "react";
import { Menu, X, Home, Rocket, PenTool, Zap, Briefcase, User } from "lucide-react";
import { Menu, X, Home, Rocket, PenTool, User, Briefcase, Clock3 } from "lucide-react";
import { defaultLang } from "@/i18n/ui";
import { type GlassHeaderProps } from "@/types";
import { motion } from "framer-motion";
@@ -14,13 +14,53 @@ import { cn } from "@/lib/utils";
export default function GlassHeader({ lang: propLang }: GlassHeaderProps) {
const [lang, setLang] = useState<Lang>(propLang || defaultLang);
const [currentPath, setCurrentPath] = useState("");
const normalizePath = (path: string) => {
const clean = path.split("#")[0].split("?")[0];
if (!clean) return "/";
if (clean === "/") return "/";
return clean.endsWith("/") ? clean.slice(0, -1) : clean;
};
useEffect(() => {
setCurrentPath(window.location.pathname);
const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== lang)) {
setLang(htmlLang);
}
const updatePath = () => setCurrentPath(normalizePath(window.location.pathname));
updatePath();
const syncLang = () => {
const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== lang)) {
setLang(htmlLang);
}
};
syncLang();
const originalPushState = history.pushState.bind(history);
const originalReplaceState = history.replaceState.bind(history);
history.pushState = function (...args) {
originalPushState(...args);
window.dispatchEvent(new Event("locationchange"));
};
history.replaceState = function (...args) {
originalReplaceState(...args);
window.dispatchEvent(new Event("locationchange"));
};
window.addEventListener("popstate", updatePath);
window.addEventListener("locationchange", updatePath);
document.addEventListener("astro:after-swap", updatePath as EventListener);
document.addEventListener("astro:page-load", updatePath as EventListener);
document.addEventListener("astro:page-load", syncLang as EventListener);
return () => {
history.pushState = originalPushState;
history.replaceState = originalReplaceState;
window.removeEventListener("popstate", updatePath);
window.removeEventListener("locationchange", updatePath);
document.removeEventListener("astro:after-swap", updatePath as EventListener);
document.removeEventListener("astro:page-load", updatePath as EventListener);
document.removeEventListener("astro:page-load", syncLang as EventListener);
};
}, [propLang, lang]);
const t = useTranslations(lang);
@@ -40,18 +80,21 @@ export default function GlassHeader({ lang: propLang }: GlassHeaderProps) {
const navItems = [
{ key: 'nav.home', icon: Home, href: getLocalizedPath('/', lang) },
{ key: 'nav.about', icon: User, href: getLocalizedPath('/about', lang) },
{ key: 'nav.projects', icon: Rocket, href: getLocalizedPath('/projects', lang) },
{ key: 'nav.blog', icon: PenTool, href: getLocalizedPath('/blog', lang) },
{ key: 'nav.now', icon: Zap, href: getLocalizedPath('/now', lang) },
{ key: 'nav.now', icon: Clock3, href: getLocalizedPath('/now', lang) },
{ key: 'nav.hire', icon: Briefcase, href: getLocalizedPath('/hire', lang) },
{ key: 'nav.about', icon: User, href: getLocalizedPath('/about', lang) },
];
const isActive = (path: string) => {
if (path === '/' || path === '/zh' || path === '/zh/') {
return currentPath === path;
const normalizedPath = normalizePath(path);
if (normalizedPath === '/' || normalizedPath === '/zh') {
return currentPath === normalizedPath;
}
return currentPath.startsWith(path);
return currentPath === normalizedPath || currentPath.startsWith(`${normalizedPath}/`);
};
return (

View File

@@ -17,7 +17,15 @@ export default function LanguageSwitcher({ lang: propLang }: LanguageSwitcherPro
const [currentLang, setCurrentLang] = useState<Lang>(propLang || defaultLang);
useEffect(() => {
if (typeof document !== 'undefined') {
if (typeof window !== "undefined") {
const pathLang = window.location.pathname.startsWith("/zh") ? "zh" : "en";
if (pathLang !== currentLang) {
setCurrentLang(pathLang);
}
return;
}
if (typeof document !== "undefined") {
const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== currentLang)) {
setCurrentLang(htmlLang);
@@ -43,35 +51,14 @@ export default function LanguageSwitcher({ lang: propLang }: LanguageSwitcherPro
const handleSelectLanguage = (languageOpt: typeof availableLanguages[0]) => {
setSelectedLanguage(languageOpt);
setIsOpen(false);
if (typeof window !== 'undefined') {
if (typeof window !== "undefined") {
const currentPathname = window.location.pathname;
const currentPathParts = currentPathname.split('/').filter(p => p);
let basePath = '';
const basePath = currentPathname.replace(/^\/(en|zh)(?=\/|$)/, "") || "/";
const targetPath = getLocalizedPath(basePath, languageOpt.code);
if (currentPathParts.length > 0 && Object.keys(i18nLanguages).includes(currentPathParts[0])) {
basePath = '/' + currentPathParts.slice(1).join('/');
} else {
basePath = currentPathname;
}
if (!basePath.startsWith('/')) {
basePath = '/' + basePath;
}
if (basePath === '//') basePath = '/';
if (basePath === '') basePath = '/';
let newPath;
if (languageOpt.code === defaultLang) {
newPath = basePath;
} else {
newPath = `/${languageOpt.code}${basePath}`;
}
newPath = newPath.replace(/\/\/+/g, '/');
if (newPath === '') newPath = '/';
if (newPath !== currentPathname) {
window.location.href = newPath;
const normalize = (value: string) => value.replace(/\/+$/, "") || "/";
if (normalize(targetPath) !== normalize(currentPathname)) {
window.location.assign(targetPath);
}
}
};
@@ -119,4 +106,4 @@ export default function LanguageSwitcher({ lang: propLang }: LanguageSwitcherPro
</AnimatePresence>
</div>
);
}
}

View File

@@ -9,16 +9,16 @@ export const translations = {
nav: {
home: 'Home',
about: 'About',
services: 'Services',
projects: 'Projects',
blog: 'Blog',
hire: 'Hire',
uses: 'Uses',
contact: 'Contact',
now: 'Now',
hire: 'Hire',
},
site: {
title: 'Joey Zhao - Full Stack Developer',
description: 'Full Stack Developer specializing in React, Node.js, and modern web technologies',
title: 'Joey Z. - AI Full-stack Engineer',
description: 'AI full-stack engineer focused on complex systems, product delivery, and long-term maintainability',
},
hero: {
greeting: "Hello, I'm",
@@ -70,7 +70,7 @@ export const translations = {
},
blog: {
slogan: 'This is where innovative thinking meets complex problems.',
backToList: 'Back to Blog',
backToList: 'Back to Writing',
},
services: {
title: 'What I Do',
@@ -113,18 +113,16 @@ export const translations = {
title: 'Contact Me 📫',
warning: 'Since everyone\'s time is valuable, please add a note when contacting me, such as: collaboration, consultation, project requirements, etc. I may not reply to or might ignore messages without notes. Thank you for your cooperation!',
methods: [
{ label: 'Email', value: 'zhaoguiyang18@gmail.com', icon: 'mail', link: 'mailto:zhaoguiyang18@gmail.com' },
{ label: 'WeChat', value: 'JoyCodeing', icon: 'wechat' },
{ label: 'QQ', value: '2770723534', icon: 'qq' },
{ label: 'Telegram', value: '@joyzhao', icon: 'send', link: 'https://t.me/joyzhao' },
{ label: 'Email', value: '2770723534@qq.com', icon: 'mail', link: 'mailto:2770723534@qq.com' },
{ label: 'Email (Secondary)', value: 'zhaoguiyang18@outlook.com', icon: 'mail', link: 'mailto:zhaoguiyang18@outlook.com' },
{ label: 'WeChat', value: 'Joey_Zhao_dev', icon: 'wechat' },
{ label: 'Telegram', value: 'joey_zgy', icon: 'send', link: 'https://t.me/joey_zgy' },
],
},
socials: {
title: 'Community & Media 🌐',
items: [
{ label: 'GitHub', value: 'github.com/zhaoguiyang', icon: 'github', link: 'https://github.com/zhaoguiyang' },
{ label: 'Twitter / X', value: '@joyzhao', icon: 'twitter', link: 'https://twitter.com/joyzhao' },
{ label: 'LinkedIn', value: 'linkedin.com/in/zhaoguiyang', icon: 'linkedin', link: 'https://linkedin.com/in/zhaoguiyang' },
{ label: 'Blog', value: 'zhaoguiyang.site', icon: 'globe', link: 'https://zhaoguiyang.site' },
],
},
@@ -196,16 +194,16 @@ export const translations = {
nav: {
home: '首页',
about: '关于',
services: '服务',
projects: '项目',
blog: '博客',
hire: '合作',
uses: '工具',
contact: '联系',
now: '现在',
hire: '合作',
},
site: {
title: 'Joey Zhao - 全栈开发者',
description: '专注于 React、Node.js 和现代 Web 技术的全栈开发者',
title: 'Joey Z. - AI 全栈工程师',
description: '专注复杂系统、产品交付与长期可维护性的 AI 全栈工程师',
},
hero: {
greeting: '你好,我是',
@@ -257,7 +255,7 @@ export const translations = {
},
blog: {
slogan: '这里是创新思维与复杂问题相遇的地方。',
backToList: '返回博客列表',
backToList: '返回写作列表',
},
services: {
title: '我能做什么',
@@ -268,7 +266,7 @@ export const translations = {
description: '了解更多关于我的背景、技能和经验',
intro: {
title: '简介',
content: '我是Joey Zhao, 一名Ts全栈工程师也是本站的站长。\n\n热爱生活、阅读以及编程寻找远程工作的机会探索自由职业的可能性。\n喜欢自驾去追寻那些美丽的风景尝试新的事物。',
content: '我是Joey Z., 一名Ts全栈工程师也是本站的站长。\n\n热爱生活、阅读以及编程寻找远程工作的机会探索自由职业的可能性。\n喜欢自驾去追寻那些美丽的风景尝试新的事物。',
belief: '我相信这里是想法变为现实的地方。我正在探索自由职业者之路,努力成为数字游民中的一员。',
},
me: {
@@ -300,18 +298,16 @@ export const translations = {
title: '联系我 📫',
warning: '由于大家的时间都很宝贵,所以请在联系我时添加备注,如:合作、咨询、项目需求等,不予备注的我可能不会回复或直接忽略,谢谢合作!',
methods: [
{ label: '邮箱', value: 'zhaoguiyang18@gmail.com', icon: 'mail', link: 'mailto:zhaoguiyang18@gmail.com' },
{ label: '微信', value: 'JoyCodeing', icon: 'wechat' },
{ label: 'QQ', value: '2770723534', icon: 'qq' },
{ label: 'Telegram', value: '@joyzhao', icon: 'send', link: 'https://t.me/joyzhao' },
{ label: '邮箱', value: '2770723534@qq.com', icon: 'mail', link: 'mailto:2770723534@qq.com' },
{ label: '邮箱(备用)', value: 'zhaoguiyang18@outlook.com', icon: 'mail', link: 'mailto:zhaoguiyang18@outlook.com' },
{ label: '微信', value: 'Joey_Zhao_dev', icon: 'wechat' },
{ label: 'Telegram', value: 'joey_zgy', icon: 'send', link: 'https://t.me/joey_zgy' },
],
},
socials: {
title: '社区与媒体 🌐',
items: [
{ label: 'GitHub', value: 'github.com/zhaoguiyang', icon: 'github', link: 'https://github.com/zhaoguiyang' },
{ label: 'Twitter / X', value: '@joyzhao', icon: 'twitter', link: 'https://twitter.com/joyzhao' },
{ label: 'LinkedIn', value: 'linkedin.com/in/zhaoguiyang', icon: 'linkedin', link: 'https://linkedin.com/in/zhaoguiyang' },
{ label: 'Blog', value: 'zhaoguiyang.site', icon: 'globe', link: 'https://zhaoguiyang.site' },
],
},

View File

@@ -12,7 +12,7 @@ interface Props {
}
const lang = Astro.currentLocale as Lang || defaultLang;
const { title = "Rishikesh S - Portfolio", description = "My Portfolio" } =
const { title = "Joey Z. - Portfolio", description = "Engineering-focused personal website" } =
Astro.props;
const t = useTranslations(lang);
---

64
src/lib/data/contact.ts Normal file
View File

@@ -0,0 +1,64 @@
import type { ContactIntent, ContactMethod } from '@/types';
export const contactIntents: ContactIntent[] = [
{
id: 'role-opportunity',
title: {
en: 'Role Opportunity',
zh: '岗位机会沟通',
},
description: {
en: 'Open to senior frontend/full-stack roles where architecture and execution quality matter.',
zh: '欢迎资深前端/全栈岗位沟通,偏好重视架构与交付质量的团队。',
},
},
{
id: 'case-collaboration',
title: {
en: 'Case Collaboration',
zh: '专项协作讨论',
},
description: {
en: 'Best for a scoped module, architecture upgrade, or technical diagnosis with clear goals.',
zh: '适用于目标清晰的模块建设、架构升级或技术诊断类协作。',
},
},
{
id: 'knowledge-exchange',
title: {
en: 'Knowledge Exchange',
zh: '内容与经验交流',
},
description: {
en: 'Happy to discuss engineering practice, AI workflow experiments, or case-study insights.',
zh: '欢迎交流工程实践、AI 协作实验与案例复盘经验。',
},
},
];
export const contactMethods: ContactMethod[] = [
{
label: { en: 'Phone', zh: '电话' },
value: '18190678380',
href: 'tel:18190678380',
},
{
label: { en: 'Email', zh: '邮箱' },
value: '2770723534@qq.com',
href: 'mailto:2770723534@qq.com',
},
{
label: { en: 'Email (Secondary)', zh: '邮箱(备用)' },
value: 'zhaoguiyang18@outlook.com',
href: 'mailto:zhaoguiyang18@outlook.com',
},
{
label: { en: 'WeChat', zh: '微信' },
value: 'Joey_Zhao_dev',
},
{
label: { en: 'Telegram', zh: 'Telegram' },
value: 'joey_zgy',
href: 'https://t.me/joey_zgy',
},
];

View File

@@ -1,3 +1,20 @@
export { personalInfo } from './personal-info';
export { projects } from './projects';
export { services } from './services';
export { services } from './services';
export { uses } from './uses';
export { contactIntents, contactMethods } from './contact';
export {
identityFacts,
heroPrinciples,
heroSkillGroups,
brandProofPoints,
brandMethodCards,
brandTimeline,
aboutNarrative,
aboutCapabilities,
aboutPrinciples,
collaborationModels,
collaborationFitSignals,
collaborationProcess,
collaborationFaq,
} from './personal-brand';

View File

@@ -0,0 +1,415 @@
import type { LocalizedText } from '@/types';
export interface LocalizedCard {
title: LocalizedText;
description: LocalizedText;
}
export interface TimelineItem {
period: LocalizedText;
title: LocalizedText;
summary: LocalizedText;
highlights?: LocalizedText[];
}
export interface QAItem {
question: LocalizedText;
answer: LocalizedText;
}
export interface FactItem {
label: LocalizedText;
value: LocalizedText;
}
export interface SkillGroup {
name: LocalizedText;
items: string[];
}
export const identityFacts: FactItem[] = [
{
label: { en: 'Name', zh: '姓名' },
value: { en: 'Joey Z.', zh: 'Joey Z.' },
},
{
label: { en: 'Experience', zh: '工作经验' },
value: { en: '8 years in Web development', zh: '8 年 Web 开发经验' },
},
];
export const heroPrinciples: LocalizedText[] = [
{
en: 'Open to remote opportunities now. If your team is hiring, I would love to connect.',
zh: '目前寻求远程工作机会,如你正在招聘,欢迎联系我。',
},
{
en: 'Evolving from frontend toward full-stack, and building as an AI-era product engineer with an always-learning mindset.',
zh: '我正在从前端走向全栈,持续进化为 AI 时代的产品工程师,不给自己设限。',
},
];
export const heroSkillGroups: SkillGroup[] = [
{
name: { en: 'Frontend Core', zh: '前端核心' },
items: [
'React',
'Vue3',
'Next.js',
'Nuxt.js',
'TypeScript',
'SSR',
'Mini Program',
'uni-app',
'Cross-platform Development',
'Vite',
'Webpack',
],
},
{
name: { en: 'Visualization', zh: '可视化能力' },
items: ['ECharts', 'AntV', 'G6', 'Realtime Monitoring'],
},
{
name: { en: 'Full-stack', zh: '全栈协作' },
items: ['Node.js (Express/NestJS)', 'PostgreSQL', 'Redis'],
},
{
name: { en: 'Engineering', zh: '工程实践' },
items: ['Component Library', 'Code Review', 'Tech Specs', 'Delivery Process'],
},
{
name: { en: 'DevOps', zh: '运维能力' },
items: ['Linux', 'Docker', 'CI/CD', 'Jenkins'],
},
];
export const brandProofPoints: LocalizedText[] = [
{
en: '8 years across enterprise, fintech, and blockchain products',
zh: '8 年企业级、金融科技、区块链系统经验',
},
{
en: 'Hands-on lead for architecture, core modules, and engineering workflows',
zh: '可主导架构、核心模块和工程化流程落地',
},
{
en: 'Strong in complex interactions, visual systems, and high-volume data workflows',
zh: '擅长复杂交互、可视化系统和大数据量业务流程',
},
{
en: 'Combines AI tooling with delivery to improve speed and clarity',
zh: '将 AI 工具用于研发提效、项目理解与文档沉淀',
},
];
export const brandMethodCards: LocalizedCard[] = [
{
title: {
en: 'Problem Framing First',
zh: '先定义问题',
},
description: {
en: 'I align business context, technical boundaries, and delivery constraints before implementation.',
zh: '编码之前先对齐业务上下文、技术边界和交付约束。',
},
},
{
title: {
en: 'Architecture for Iteration',
zh: '为迭代设计架构',
},
description: {
en: 'I build module boundaries and component conventions so systems can evolve safely.',
zh: '通过模块边界与组件规范,确保系统能稳定演进。',
},
},
{
title: {
en: 'Execution with Transparency',
zh: '透明化执行',
},
description: {
en: 'I keep progress visible via milestones, docs, and explicit decision records.',
zh: '通过里程碑、文档和决策记录,让推进过程可视、可追踪。',
},
},
];
export const brandTimeline: TimelineItem[] = [
{
period: {
en: '2026.02 — Now',
zh: '2026.02 — 至今',
},
title: {
en: 'AI Full-stack Transition · Independent Product Building',
zh: 'AI 全栈转型 · 独立工具产品实践',
},
summary: {
en: 'Using full-stack skills to build practical tool products, while advancing AI agent development capabilities.',
zh: '以全栈能力持续开发实用型工具产品,同时系统学习并实践 AI Agent 开发能力。',
},
highlights: [
{
en: 'From frontend specialization toward AI full-stack product engineering',
zh: '从前端专项能力向 AI 全栈产品工程能力升级',
},
{
en: 'Combining product thinking with engineering execution in AI-era workflows',
zh: '在 AI 时代工作流中融合产品思维与工程落地能力',
},
],
},
{
period: {
en: '2025.07 — 2026.01',
zh: '2025.07 — 2026.01',
},
title: {
en: 'Frontend Tech Lead · Company A',
zh: '前端技术负责人 · 某技术公司 A',
},
summary: {
en: 'Led complex frontend refactor, visualization modules, and AI-assisted project analysis under weak documentation.',
zh: '在文档薄弱场景下主导复杂前端重构、可视化模块建设和 AI 辅助代码分析。',
},
highlights: [
{
en: 'Optimized engineering structure and build workflow for better stability',
zh: '优化前端工程结构与构建流程,提升系统稳定性',
},
{
en: 'Generated technical docs via AI-assisted analysis to reduce onboarding cost',
zh: '通过 AI 工具梳理技术说明,降低团队理解和上手成本',
},
],
},
{
period: {
en: '2021.03 — 2025.06',
zh: '2021.03 — 2025.06',
},
title: {
en: 'Frontend Engineer · Company B',
zh: '前端开发 · 某科技公司 B',
},
summary: {
en: 'Led architecture for 3 core systems and delivered from zero to one with Vue3 + TypeScript.',
zh: '主导 3 个核心项目架构设计,使用 Vue3 + TypeScript 推进从 0 到 1 建设。',
},
highlights: [
{
en: 'Built 30+ reusable business components and internal component library',
zh: '沉淀 30+ 通用业务组件并构建内部组件库',
},
{
en: 'Drove technical standards and team engineering sharing',
zh: '制定技术规范并持续推动团队工程实践分享',
},
],
},
{
period: {
en: '2020.10 — 2021.03',
zh: '2020.10 — 2021.03',
},
title: {
en: 'Frontend Engineer · Company C',
zh: '前端开发 · 某科技公司 C',
},
summary: {
en: 'Owned architecture selection for 2 blockchain systems and built key reusable components.',
zh: '主导 2 个区块链项目架构选型并封装核心业务组件。',
},
highlights: [
{
en: 'Wrote onboarding manual and trained 5 new engineers',
zh: '编写前端新人手册并完成 5 名新员工培训',
},
],
},
{
period: {
en: '2019.04 — 2020.09',
zh: '2019.04 — 2020.09',
},
title: {
en: 'Frontend Team Lead · Company D',
zh: '前端组长 · 某科技公司 D',
},
summary: {
en: 'Set up a unified multi-end frontend framework for app, admin, mini-program, and public account.',
zh: '搭建多端统一前端开发框架,覆盖 App、管理后台、小程序与公众号。',
},
highlights: [
{
en: 'Pushed coding/Git conventions and delivered 8 projects on schedule',
zh: '推动代码与 Git 规范落地,支撑 8 个项目按时上线',
},
],
},
{
period: {
en: '2018.03 — 2019.03',
zh: '2018.03 — 2019.03',
},
title: {
en: 'Frontend Engineer · Company E',
zh: '前端开发 · 某科技公司 E',
},
summary: {
en: 'Maintained 3 core products across Web and mobile while ensuring stable iterative delivery.',
zh: '参与 Web 与移动端项目开发,维护 3 个核心产品并保障系统稳定迭代。',
},
},
];
export const aboutNarrative: LocalizedText[] = [
{
en: 'I focus on complex business systems where architecture and long-term maintainability are critical.',
zh: '我长期专注复杂业务系统,核心关注点是架构质量和长期可维护性。',
},
{
en: 'My strengths combine frontend architecture, engineering processes, and cross-role collaboration.',
zh: '我的优势在于前端架构能力、工程化推进能力和跨角色协作。',
},
{
en: 'This website is my long-term knowledge base for projects, methods, and practical lessons.',
zh: '这个网站会持续沉淀项目案例、工作方法与可复用经验。',
},
];
export const aboutCapabilities: LocalizedText[] = [
{
en: 'Complex frontend system architecture and module governance',
zh: '复杂前端系统架构与模块治理',
},
{
en: 'Data visualization and real-time interaction system implementation',
zh: '数据可视化与实时交互系统开发',
},
{
en: 'From-zero-to-one product frontend setup and engineering standardization',
zh: '从 0 到 1 前端体系建设与工程规范落地',
},
{
en: 'AI-assisted code understanding, documentation, and troubleshooting',
zh: 'AI 辅助代码理解、文档沉淀与问题排查',
},
];
export const aboutPrinciples: LocalizedText[] = [
{
en: 'Prefer clear architecture over temporary speed gains.',
zh: '短期速度可以妥协,但架构清晰度不能妥协。',
},
{
en: 'Treat delivery quality as a system, not a one-time output.',
zh: '交付质量是系统能力,不是一次性产出。',
},
{
en: 'Document decisions to reduce team communication cost.',
zh: '关键决策文档化,降低团队协作成本。',
},
];
export const collaborationModels: LocalizedCard[] = [
{
title: {
en: 'Role Collaboration',
zh: '岗位型协作',
},
description: {
en: 'Join as a senior frontend/full-stack contributor for medium to long-term product building.',
zh: '以资深前端/全栈角色加入团队,参与中长期产品建设。',
},
},
{
title: {
en: 'Case-based Delivery',
zh: '专项课题交付',
},
description: {
en: 'Deliver a scoped module, architecture upgrade, or performance optimization initiative.',
zh: '围绕明确范围的模块建设、架构升级或性能优化进行阶段性交付。',
},
},
{
title: {
en: 'Advisory Session',
zh: '顾问与评审',
},
description: {
en: 'Provide technical diagnosis and implementation guidance for difficult engineering problems.',
zh: '面向复杂工程问题提供技术诊断与实施建议。',
},
},
];
export const collaborationFitSignals: LocalizedText[] = [
{
en: 'You value long-term maintainability, not just quick patching.',
zh: '你重视长期可维护性,而不仅是短期修补。',
},
{
en: 'Your team needs a reliable engineer to own ambiguous, complex modules.',
zh: '你的团队需要有人能接住复杂且边界不清的核心模块。',
},
{
en: 'You prefer transparent communication and documented decisions.',
zh: '你希望协作过程透明,关键决策可追溯。',
},
];
export const collaborationProcess: LocalizedText[] = [
{
en: 'Align context and expected outcomes',
zh: '对齐上下文与预期结果',
},
{
en: 'Define scope, constraints, and ownership boundaries',
zh: '明确范围、约束与责任边界',
},
{
en: 'Execute in visible milestones with regular updates',
zh: '以可见里程碑推进并定期同步',
},
{
en: 'Review outcomes and capture reusable learnings',
zh: '复盘结果并沉淀可复用经验',
},
];
export const collaborationFaq: QAItem[] = [
{
question: {
en: 'Do you only do project-based work?',
zh: '你只做项目制合作吗?',
},
answer: {
en: 'No. Role collaboration and long-term engineering participation are both welcome.',
zh: '不是。岗位型长期协作和阶段性项目合作都可以。',
},
},
{
question: {
en: 'What should I send before the first discussion?',
zh: '第一次沟通前需要准备什么?',
},
answer: {
en: 'A short context summary, desired outcome, timeline, and current blockers are enough to start.',
zh: '提供背景、目标、时间窗口和当前卡点即可开始沟通。',
},
},
{
question: {
en: 'Can we start with a small scope first?',
zh: '可以先从小范围开始吗?',
},
answer: {
en: 'Yes. A small pilot is often the fastest way to validate collaboration fit.',
zh: '可以。先做小范围试点通常是验证匹配度最快的方式。',
},
},
];

View File

@@ -1,46 +1,47 @@
import type { PersonalInfo } from '@/types';
export const personalInfo: PersonalInfo = {
name: "Joey Zhao",
location: "China",
avatar: "https://avatars.githubusercontent.com/u/24975063?v=4",
email: "zhaoguiyang18@gmail.com",
github: "https://github.com/zguiyang",
linkedin: "https://linkedin.com/in/zhaoguiyang",
website: "https://zhaoguiyang.com",
twitter: "https://twitter.com/zhaoguiyang",
name: 'Joey Z.',
location: 'Chengdu, China',
avatar: 'https://avatars.githubusercontent.com/u/24975063?v=4',
email: '2770723534@qq.com',
github: 'https://github.com/zguiyang',
linkedin: '',
website: 'https://zhaoguiyang.com',
twitter: '',
position: {
en: "Full-stack + AI Product Builder",
zh: "全栈 + AI 产品构建者"
en: 'AI Full-stack Engineer',
zh: 'AI 全栈工程师',
},
description: {
en: "Building AI-powered products and turning ambitious ideas into reality. Currently focused on Elynd - an open AI workspace for builders.",
zh: "构建 AI 驱动产品,将雄心勃勃的想法变为现实。目前专注于 Elynd - 一个面向构建者的开放 AI 工作空间。"
en: '8 years building enterprise systems, financial platforms, and blockchain infrastructure. Focused on architecture discipline, delivery quality, and long-term maintainability.',
zh: '8 年企业系统、金融平台与区块链基础设施研发经验,关注架构纪律、交付质量与长期可维护性。',
},
about: {
en: [
"I started my programming journey in 2016 when I accidentally encountered programming. This opportunity became the catalyst for my coding adventure, and I fell in love with programming during my subsequent self-learning journey.",
"I enjoy creating websites and applications with code and sharing them with users. The sense of achievement I get from this process makes me increasingly fascinated. My dream is to become a lifelong coder.",
"I love reading, traveling (especially road trips), enjoying various cuisines (particularly hot pot), and playing mobile games like League of Legends, Honor of Kings, and PUBG Mobile."
'I build reliable web systems with clear module boundaries and strong delivery ownership.',
'My core experience comes from enterprise, fintech, and blockchain product domains.',
'I use this site as an evolving knowledge base of projects, decisions, and practical learnings.',
],
zh: [
"在2016年一次偶然的机会中接触到了编程以此为契机开始了我的编程之旅并在后续的自学之旅中爱上了编程。",
"我喜欢用代码编写出一个个的网页、应用,分享给用户使用,这其中获得的成就感让我愈发着迷。梦想是成为一名终身编码者。",
"热爱生活、阅读以及编程,寻找远程工作的机会,探索自由职业的可能性。喜欢自驾,去追寻那些美丽的风景,尝试新的事物。"
]
'我专注构建稳定、可维护、可扩展的 Web 系统,并对交付结果负责。',
'核心经验来自企业级、金融科技与区块链产品场景。',
'我把这个网站当作持续更新的公开工作台,记录案例、方法与实践复盘。',
],
},
stats: {
repositories: 152,
commits: "42K",
contributions: 87
commits: '42K',
contributions: 87,
},
skills: {
frontend: ["HTML", "CSS", "JavaScript", "Vue.js", "React.js", "TypeScript"],
backend: ["Node.js"],
database: ["MySQL", "MongoDB"],
devops: ["Linux", "Git", "Docker", "Nginx", "Apache"],
mobile: ["React Native", "Flutter"]
frontend: ['TypeScript', 'React', 'Vue', 'Astro'],
backend: ['Node.js', 'NestJS', 'Fastify'],
database: ['PostgreSQL', 'MySQL', 'MongoDB'],
devops: ['Linux', 'Git', 'Docker', 'CI/CD', 'Jenkins', 'Nginx'],
mobile: ['React Native', 'Flutter'],
},
terminal: {
username: "joy@dev-workspace"
}
};
username: 'joy@engineer',
},
};

View File

@@ -1,207 +1,260 @@
import type { Project } from '@/types';
const sharedCases: Project[] = [
{
id: 'digital-securities-platform',
featured: true,
type: 'product',
status: 'completed',
role: 'Frontend Architecture Lead',
impact: 'Delivered a stable trading platform for multi-role financial institutions',
systemType: 'Financial blockchain trading platform',
context: 'A digital securities trading system for regulated institutions and operations teams.',
title: 'Digital Securities Trading Platform',
icon: 'Landmark',
color: 'blue',
image: {
bg: 'from-blue-500/20 to-indigo-600/20',
hover: 'from-blue-500/30 to-indigo-600/30',
text: 'text-blue-500',
},
challenges: [
'Complex multi-role permission boundaries',
'High reliability requirements for trading operations',
'Integration with blockchain wallet services',
],
responsibilities: [
'Designed frontend module architecture and domain boundaries',
'Led user management and permission refactor',
'Integrated bank login and compliance-related flows',
],
outcomes: [
'Improved operational stability with reliable release rhythm',
'Reduced permission-related defects through clearer boundaries',
'Supported business launch for institutional users',
],
description: [
'A digital asset trading system supporting regulated workflows.',
'Built for role-based operations, auditing, and risk-sensitive processes.',
],
tech: ['TypeScript', 'React', 'Node.js', 'Permission System'],
link: '#',
},
{
id: 'baas-platform',
featured: true,
type: 'product',
status: 'completed',
role: 'Senior Frontend Engineer',
impact: 'Enabled enterprise clients to manage blockchain services efficiently',
systemType: 'Blockchain BaaS platform',
context: 'Enterprise-grade blockchain service platform for chain management and operations.',
title: 'Blockchain BaaS Platform',
icon: 'Blocks',
color: 'violet',
image: {
bg: 'from-violet-500/20 to-fuchsia-500/20',
hover: 'from-violet-500/30 to-fuchsia-500/30',
text: 'text-violet-500',
},
challenges: [
'High-density operational console information',
'Complex asynchronous task status visibility',
'Multi-environment configuration consistency',
],
responsibilities: [
'Built scalable admin UI architecture for service management',
'Implemented observable task state flows and feedback loops',
'Drove reusable component patterns across modules',
],
outcomes: [
'Improved operation efficiency of chain service workflows',
'Reduced repetitive UI implementation with reusable patterns',
'Increased delivery speed in subsequent modules',
],
description: [
'A platform for enterprise blockchain network and service operations.',
'Focused on maintainability and operational efficiency.',
],
tech: ['React', 'TypeScript', 'Ant Design', 'WebSocket'],
link: '#',
},
{
id: 'supply-chain-finance',
featured: true,
type: 'client',
status: 'completed',
role: 'Full-stack Engineer',
impact: 'Supported finance workflows for upstream and downstream participants',
systemType: 'Supply chain finance platform',
context: 'A collaborative system connecting enterprises, finance teams, and partner organizations.',
title: 'Supply Chain Finance Platform',
icon: 'Network',
color: 'emerald',
image: {
bg: 'from-emerald-500/20 to-teal-500/20',
hover: 'from-emerald-500/30 to-teal-500/30',
text: 'text-emerald-500',
},
challenges: [
'Long business流程 with many states and edge cases',
'Cross-role data visibility requirements',
'Need for high traceability in approvals and operations',
],
responsibilities: [
'Implemented core workflow modules and state transitions',
'Designed role-specific views and permission controls',
'Optimized key pages for faster operational handling',
],
outcomes: [
'Improved workflow transparency across participants',
'Reduced process handover friction in finance operations',
'Enabled stable iterative delivery for business expansion',
],
description: [
'A multi-role financial workflow system for supply chain participants.',
'Focused on process clarity, reliability, and operations efficiency.',
],
tech: ['TypeScript', 'React', 'Node.js', 'PostgreSQL'],
link: '#',
},
{
id: 'smart-park-system',
featured: false,
type: 'client',
status: 'completed',
role: 'Frontend Engineer',
impact: 'Integrated business modules for industrial operations management',
systemType: 'Industrial smart park management system',
context: 'A digital management system for operations, resources, and service workflows in industrial parks.',
title: 'Industrial Smart Park Management System',
icon: 'Building2',
color: 'orange',
image: {
bg: 'from-orange-500/20 to-amber-500/20',
hover: 'from-orange-500/30 to-amber-500/30',
text: 'text-orange-500',
},
challenges: [
'Many business modules with different usage patterns',
'Dashboard readability for operation teams',
'Need for maintainable long-term module evolution',
],
responsibilities: [
'Developed key modules and unified UI interaction patterns',
'Improved dashboard information hierarchy and readability',
'Collaborated with backend and product teams for iterative delivery',
],
outcomes: [
'Improved operator experience in daily management tasks',
'Reduced UI inconsistency across modules',
'Supported long-term maintainability for continued delivery',
],
description: [
'A comprehensive system for industrial park operations and services.',
'Designed for clarity, consistency, and scalable module growth.',
],
tech: ['React', 'TypeScript', 'ECharts', 'REST API'],
link: '#',
},
{
id: 'elynd',
featured: false,
type: 'experiment',
status: 'building',
role: 'Founder & Developer',
impact: 'Exploring AI-assisted development workflows in production-like scenarios',
systemType: 'AI-assisted workspace',
context: 'An open workspace product exploring practical AI collaboration in software development.',
title: 'Elynd',
icon: 'Zap',
color: 'purple',
image: {
bg: 'from-purple-500/20 to-blue-500/20',
hover: 'from-purple-500/30 to-blue-500/30',
text: 'text-purple-500',
},
challenges: ['Designing useful AI workflows without adding process burden'],
responsibilities: ['Product exploration and end-to-end implementation'],
outcomes: ['Validated several practical AI-assisted development patterns'],
description: [
'An open AI workspace for builders.',
'Used as an R&D track, not the primary professional narrative.',
],
coverImage: '',
coverImageAlt: 'Elynd project cover',
tech: ['TypeScript', 'React', 'AI Workflow', 'Open Source'],
link: '#',
},
];
export const projects = {
en: [
{
id: "elynd",
featured: true,
type: "product",
status: "building",
role: "Founder & Lead Developer",
impact: "Building an open AI workspace for builders",
title: "Elynd",
icon: "Zap",
color: "purple",
image: {
bg: "from-purple-500/20 to-blue-500/20",
hover: "from-purple-500/30 to-blue-500/30",
text: "text-purple-400",
},
description: [
"An open AI workspace for builders to work smarter.",
"Built with AI-first principles, making AI your co-builder.",
"Fully open source and self-hostable.",
"Currently in active development with early access coming soon.",
],
tech: ["TypeScript", "React", "AI/ML", "Open Source"],
link: "#",
links: {
demo: "#",
github: "https://github.com",
},
},
{
id: "taskify",
type: "client",
status: "completed",
role: "Lead Developer",
impact: "Helped startup launch MVP in 3 months",
title: "Taskify App",
tag: "business",
icon: "Smartphone",
color: "purple",
image: {
bg: "from-purple-500/20 to-purple-600/20",
hover: "from-purple-500/20 to-purple-600/20",
text: "text-purple-400",
},
description: [
"A comprehensive task management application with drag-and-drop functionality.",
"Built with React, TypeScript, and Tailwind CSS using modern development approaches.",
"Real-time collaboration through WebSocket integration for instant updates.",
"Advanced task filtering, sorting, and project management capabilities.",
],
tech: ["React", "Node.js", "MongoDB"],
link: "#",
},
{
id: "eshop",
type: "client",
status: "completed",
role: "Full-stack Developer",
impact: "E-commerce platform handling $100K+ monthly revenue",
title: "E-Shop Platform",
tag: "business",
icon: "ShoppingCart",
color: "green",
image: {
bg: "from-green-500/20 to-green-600/20",
hover: "from-green-500/20 to-green-600/20",
text: "text-green-400",
},
description: [
"Scalable e-commerce platform built with Next.js, Stripe payments, and TailwindCSS.",
"Mobile-first responsive design for optimal user experience across devices.",
"Integrated payment processing with Stripe for secure transactions.",
"Admin dashboard for inventory management and order processing.",
],
tech: ["Next.js", "Stripe", "TailwindCSS"],
link: "#",
},
{
id: "portfolio",
type: "experiment",
status: "completed",
role: "Creator",
impact: "Personal brand and portfolio showcasing",
title: "Portfolio Site",
tag: "portfolio",
icon: "Globe",
color: "purple",
image: {
bg: "from-purple-500/20 to-purple-600/20",
hover: "from-purple-500/20 to-purple-600/20",
text: "text-purple-400",
},
description: [
"Personal portfolio showcasing my work, built with HTML, TailwindCSS, and Alpine.js.",
"Responsive design with dark mode support and smooth animations.",
"Optimized for performance with minimal JavaScript and efficient CSS.",
"Integrated blog section with markdown support for content management.",
],
tech: ["HTML", "TailwindCSS", "Alpine.js"],
link: "#",
},
],
en: sharedCases,
zh: [
{
id: "elynd",
featured: true,
type: "product",
status: "building",
role: "创始人 & 首席开发者",
impact: "构建面向构建者的开放 AI 工作空间",
title: "Elynd",
icon: "Zap",
color: "purple",
image: {
bg: "from-purple-500/20 to-blue-500/20",
hover: "from-purple-500/30 to-blue-500/30",
text: "text-purple-400",
},
description: [
"一个面向构建者的开放 AI 工作空间。",
"从 AI 第一性原理出发,让 AI 成为你的共同构建者。",
"完全开源,可自托管。",
"目前正在积极开发中,早期访问即将推出。",
],
tech: ["TypeScript", "React", "AI/ML", "开源"],
link: "#",
links: {
demo: "#",
github: "https://github.com",
},
...sharedCases[0],
role: '前端架构负责人',
impact: '为多角色金融机构交付稳定的交易平台',
systemType: '数字证券金融区块链交易平台',
context: '面向受监管机构与运营团队的数字证券交易系统。',
title: '数字证券交易平台',
challenges: ['多角色复杂权限边界', '交易场景下的高稳定性要求', '区块链钱包服务集成'],
responsibilities: ['设计前端模块化架构与领域边界', '主导用户与权限体系重构', '接入银行登录与合规相关流程'],
outcomes: ['提升系统稳定性与发版可控性', '通过边界治理降低权限类缺陷', '支撑机构端业务落地'],
description: ['支持受监管流程的数字资产交易系统。', '强调角色权限、审计可追踪和风险敏感流程。'],
tech: ['TypeScript', 'React', 'Node.js', '权限系统'],
},
{
id: "taskify",
type: "client",
status: "completed",
role: "首席开发者",
impact: "帮助创业公司在3个月内推出 MVP",
title: "Taskify 应用",
tag: "business",
icon: "Smartphone",
color: "purple",
image: {
bg: "from-purple-500/20 to-purple-600/20",
hover: "from-purple-500/20 to-purple-600/20",
text: "text-purple-400",
},
description: [
"一个全面的任务管理应用,具有拖放功能。",
"使用React、TypeScript和Tailwind CSS构建采用现代开发方法。",
"通过WebSocket集成实现实时协作功能实现即时更新。",
"高级任务筛选、排序和项目管理功能。",
],
tech: ["React", "Node.js", "MongoDB"],
link: "#",
...sharedCases[1],
role: '资深前端工程师',
impact: '帮助企业客户高效管理区块链服务',
systemType: '区块链 BaaS 平台',
context: '面向企业的链服务管理与运维平台。',
title: '区块链 BaaS 平台',
challenges: ['高密度运维信息的可读性', '异步任务状态可观察性', '多环境配置一致性'],
responsibilities: ['构建可扩展的管理端前端架构', '实现可追踪任务状态反馈链路', '推动跨模块可复用组件方案'],
outcomes: ['提升链服务运维效率', '减少重复 UI 开发成本', '提高后续模块迭代速度'],
description: ['面向企业区块链网络与服务运维的平台。', '重点优化可维护性与操作效率。'],
tech: ['React', 'TypeScript', 'Ant Design', 'WebSocket'],
},
{
id: "eshop",
type: "client",
status: "completed",
role: "全栈开发者",
impact: "电商平台月营收超过 10 万美元",
title: "电商平台",
tag: "business",
icon: "ShoppingCart",
color: "green",
image: {
bg: "from-green-500/20 to-green-600/20",
hover: "from-green-500/20 to-green-600/20",
text: "text-green-400",
},
description: [
"使用Next.js、Stripe支付和TailwindCSS构建的可扩展电子商务平台。",
"采用移动优先的响应式设计,提供最佳用户体验。",
"集成Stripe支付处理确保交易安全。",
"管理员仪表板,用于库存管理和订单处理。",
],
tech: ["Next.js", "Stripe", "TailwindCSS"],
link: "#",
...sharedCases[2],
role: '全栈工程师',
impact: '支撑上下游参与方的金融协作流程',
systemType: '供应链金融平台',
context: '连接企业、金融团队与合作方的协同业务系统。',
title: '供应链金融平台',
challenges: ['长链路流程状态与边界处理', '跨角色数据可见性策略', '审批与操作的高可追踪性'],
responsibilities: ['实现核心流程模块与状态流转', '设计角色化视图与权限控制', '优化关键页面操作效率'],
outcomes: ['提升参与方流程透明度', '降低金融业务交接摩擦', '支撑业务扩展中的稳定迭代'],
description: ['面向多角色协同的金融业务流程系统。', '强调流程清晰、可靠与高效操作。'],
tech: ['TypeScript', 'React', 'Node.js', 'PostgreSQL'],
},
{
id: "portfolio",
type: "experiment",
status: "completed",
role: "创作者",
impact: "个人品牌和作品集展示",
title: "个人作品集网站",
tag: "portfolio",
icon: "Globe",
color: "purple",
image: {
bg: "from-purple-500/20 to-purple-600/20",
hover: "from-purple-500/20 to-purple-600/20",
text: "text-purple-400",
},
description: [
"展示我的作品的个人作品集使用HTML、TailwindCSS和Alpine.js构建。",
"响应式设计,支持暗黑模式和平滑动画。",
"通过最小化JavaScript和高效CSS优化性能。",
"集成博客部分支持markdown内容管理。",
],
tech: ["HTML", "TailwindCSS", "Alpine.js"],
link: "#",
...sharedCases[3],
role: '前端工程师',
impact: '整合园区管理核心业务模块',
systemType: '工业智慧园区管理系统',
context: '覆盖园区运营、资源和服务流程的数字化管理平台。',
title: '工业智慧园区管理系统',
challenges: ['多业务模块交互差异大', '运营看板信息层级复杂', '模块长期演进可维护性要求高'],
responsibilities: ['开发关键模块并统一交互模式', '优化看板信息层级与可读性', '与后端及产品团队协同迭代交付'],
outcomes: ['提升日常运营管理效率', '减少跨模块 UI 不一致问题', '支撑长期可维护迭代'],
description: ['园区运营与服务协同的一体化系统。', '强调清晰性、一致性与可扩展演进。'],
tech: ['React', 'TypeScript', 'ECharts', 'REST API'],
},
{
...sharedCases[4],
role: '创始人 & 开发者',
impact: '在真实场景中探索 AI 协作开发工作流',
systemType: 'AI 协作工作空间',
context: '探索 AI 在软件工程中可落地协作方式的开放产品。',
title: 'Elynd',
challenges: ['在不增加流程负担前提下设计有效 AI 工作流'],
responsibilities: ['产品探索与端到端实现'],
outcomes: ['验证多种可实践的 AI 协作开发模式'],
description: ['一个面向构建者的开放 AI 工作空间。', '作为研发探索方向,不作为职业主叙事。'],
tech: ['TypeScript', 'React', 'AI 协作', '开源'],
},
],
};

39
src/lib/data/uses.ts Normal file
View File

@@ -0,0 +1,39 @@
import type { UsesGroup } from '@/types';
export const uses: UsesGroup[] = [
{
title: {
en: 'Editor',
zh: '编辑器',
},
items: ['VS Code', 'Cursor', 'Zed'],
},
{
title: {
en: 'Languages',
zh: '语言',
},
items: ['TypeScript', 'JavaScript', 'SQL', 'Bash'],
},
{
title: {
en: 'Frameworks',
zh: '框架',
},
items: ['React', 'Astro', 'Node.js', 'Vue 3'],
},
{
title: {
en: 'Infrastructure',
zh: '基础设施',
},
items: ['Docker', 'Nginx', 'Linux', 'PostgreSQL'],
},
{
title: {
en: 'AI Workflow',
zh: 'AI 协作工作流',
},
items: ['ChatGPT', 'Claude', 'Codex', 'Agentic development workflow'],
},
];

View File

@@ -1,220 +1,127 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import HighlightBox from "@/components/markdown/HighlightBox.astro";
import { useTranslations } from "@/i18n/utils";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import {
Sparkles,
Heart,
Zap,
Mail,
MessageSquare,
Send,
Github,
Twitter,
Linkedin,
Globe
} from "lucide-react";
const IconMap: Record<string, any> = {
mail: Mail,
wechat: MessageSquare,
qq: MessageSquare, // Lucide doesn't have QQ, using MessageSquare
send: Send,
github: Github,
twitter: Twitter,
linkedin: Linkedin,
globe: Globe,
};
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import ContactCard from '@/components/ContactCard.astro';
import {
personalInfo,
uses,
aboutNarrative,
aboutCapabilities,
aboutPrinciples,
brandTimeline,
} from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
const lang = (Astro.currentLocale as Lang) || defaultLang;
const t = useTranslations(lang);
const pageTitle = t("about.title");
const isZh = lang === 'zh';
const prefix = isZh ? '/zh' : '';
---
<Layout title={pageTitle}>
<GlassHeader client:load transition:persist="header" lang={lang} />
<main class="min-h-screen relative overflow-hidden pt-16 sm:pt-20">
<!-- Background Decor -->
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
<div class="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 dark:from-primary/10 dark:via-transparent dark:to-primary/5"></div>
</div>
<Layout title={isZh ? '关于' : 'About'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<!-- Hero Section -->
<section class="relative z-10 py-16 md:py-24">
<Container>
<div class="max-w-4xl mx-auto">
<div class="flex flex-col md:flex-row items-center gap-12 mb-16">
<div class="relative group">
<div class="absolute -inset-1 rounded-full bg-gradient-to-r from-primary to-purple-600 opacity-75 blur transition duration-1000 group-hover:opacity-100 group-hover:duration-200"></div>
<div class="relative h-48 w-48 rounded-full border-4 border-background overflow-hidden bg-muted">
<img
src="/avatar.png"
alt="Joey Zhao"
class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110"
onerror="this.src='https://ui-avatars.com/api/?name=Joy+Zhao&background=0D8ABC&color=fff&size=200'"
/>
</div>
</div>
<div class="text-center md:text-left space-y-6">
<h1 class="text-4xl md:text-6xl font-bold tracking-tight">
<span class="bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent">
{t('about.title')}
</span>
</h1>
<p class="text-xl text-muted-foreground leading-relaxed">
{t('about.description')}
</p>
</div>
</div>
<div class="grid gap-8">
<!-- Intro Box -->
<HighlightBox type="tip" title={t('about.intro.title')}>
<div class="space-y-4 py-2">
<p class="text-lg leading-relaxed whitespace-pre-line">{t('about.intro.content')}</p>
<div class="p-4 rounded-xl bg-primary/5 border border-primary/10 italic text-primary/80">
{t('about.intro.belief')}
</div>
</div>
</HighlightBox>
<!-- Self Intro -->
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Sparkles className="w-6 h-6 text-primary" />
{t('about.me.title')}
</h2>
<div class="prose dark:prose-invert max-w-none">
<p class="text-lg leading-relaxed text-muted-foreground whitespace-pre-line">
{t('about.me.content')}
</p>
</div>
</div>
<!-- Skills Grid -->
<div class="grid md:grid-cols-2 gap-8">
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Zap className="w-6 h-6 text-yellow-500" />
{t('about.skills.mastered.title')}
</h2>
<div class="flex flex-wrap gap-2">
{(t('about.skills.mastered.items') as unknown as string[]).map((item: string) => (
<div class="px-3 py-1.5 rounded-lg bg-primary/5 border border-primary/10 text-muted-foreground text-xs font-medium">
{item}
</div>
))}
</div>
</div>
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Heart className="w-6 h-6 text-red-500" />
{t('about.skills.learning.title')}
</h2>
<div class="flex flex-wrap gap-2">
{(t('about.skills.learning.items') as unknown as string[]).map((item: string) => (
<div class="px-3 py-1.5 rounded-lg bg-muted/30 border border-border/50 text-muted-foreground/70 text-xs font-medium">
{item}
</div>
))}
</div>
</div>
</div>
<!-- Interests -->
<div class="space-y-8">
<h2 class="text-2xl font-bold flex items-center gap-3 px-2">
<Heart className="w-6 h-6 text-red-500" />
{t('about.interests.title')}
</h2>
<ul class="grid sm:grid-cols-2 gap-4">
{(t('about.interests.items') as unknown as any[]).map((item) => (
<li class="flex items-start gap-4 p-4 rounded-xl border border-border/40 bg-card/20 hover:bg-card/40 transition-colors group">
<div class="mt-1.5 w-1.5 h-1.5 rounded-full bg-primary/40 group-hover:bg-primary transition-colors shrink-0" />
<div class="space-y-1">
<span class="font-bold text-foreground">{item.title}</span>
<p class="text-sm text-muted-foreground leading-relaxed">{item.content}</p>
</div>
</li>
))}
</ul>
</div>
<!-- Socials & Community -->
<div class="space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3 px-2">
<Globe className="w-6 h-6 text-primary" />
{t('about.socials.title')}
</h2>
<div class="flex flex-wrap gap-4 px-2">
{(t('about.socials.items') as unknown as any[]).map((item) => {
const Icon = IconMap[item.icon] || Globe;
return (
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-2 px-4 py-2 rounded-lg bg-muted/30 border border-border/50 hover:border-primary/50 hover:bg-primary/5 transition-all group text-sm font-medium"
>
<Icon className="w-4 h-4 text-muted-foreground group-hover:text-primary transition-colors" />
<span>{item.label}</span>
</a>
);
})}
</div>
</div>
<!-- Contact -->
<div class="space-y-8 py-8">
<div class="px-2 space-y-4">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Mail className="w-6 h-6 text-primary" />
{t('about.contact.title')}
</h2>
<p class="text-muted-foreground leading-relaxed max-w-2xl">
{t('about.contact.warning')}
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{(t('about.contact.methods') as unknown as any[]).map((method) => {
const Icon = IconMap[method.icon] || MessageSquare;
const content = (
<div class="flex items-center gap-4 p-4 rounded-xl border border-border/40 bg-card/20 hover:bg-card/40 transition-all group">
<div class="p-2.5 rounded-lg bg-primary/5 text-primary/70 group-hover:bg-primary/10 group-hover:text-primary transition-colors">
<Icon className="w-5 h-5" />
</div>
<div class="flex flex-col">
<span class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest">{method.label}</span>
<span class="text-sm font-medium break-all">{method.value}</span>
</div>
</div>
);
if (method.link) {
return (
<a href={method.link} class="block transition-transform hover:-translate-y-0.5">
{content}
</a>
);
}
return <div class="cursor-default">{content}</div>;
})}
</div>
</div>
</div>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main" id="overview">
<div class="mb-6 flex flex-wrap gap-2 text-sm">
<a href="#overview" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '概览' : 'Overview'}</a>
<a href="#story" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '故事' : 'Story'}</a>
<a href="#capabilities" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '能力' : 'Capabilities'}</a>
<a href="#principles" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '原则' : 'Principles'}</a>
<a href="#timeline" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '时间线' : 'Timeline'}</a>
<a href="#uses" class="rounded-md border border-border px-3 py-1 hover:bg-muted">Uses</a>
</div>
</Container>
</section>
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '关于我' : 'About'}</h1>
<p class="mt-6 text-lg leading-relaxed text-muted-foreground">
{isZh
? '我是一名长期在复杂业务场景中工作的 AI 全栈工程师。这个页面主要展示我的经历、能力与做事方式。'
: 'I am an AI full-stack engineer working in complex product environments. This page focuses on my experience, capability, and way of working.'}
</p>
</section>
<section class="page-content-main mt-10 page-surface p-8" id="story">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '个人叙事' : 'Narrative'}</h2>
<div class="mt-4 space-y-4 text-muted-foreground">
{aboutNarrative.map((line) => <p>{line[lang]}</p>)}
</div>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="capabilities">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '核心能力' : 'Core Capabilities'}</h2>
<ul class="mt-4 grid gap-3 md:grid-cols-2 text-muted-foreground">
{aboutCapabilities.map((item) => (
<li class="flex gap-3 rounded-md border border-border/70 p-3"><span class="mt-2 h-1.5 w-1.5 rounded-full bg-primary"></span><span>{item[lang]}</span></li>
))}
</ul>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="principles">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '工作原则' : 'Working Principles'}</h2>
<ul class="mt-4 space-y-3 text-muted-foreground">
{aboutPrinciples.map((item) => (
<li class="flex gap-3"><span class="mt-2 h-1.5 w-1.5 rounded-full bg-primary"></span><span>{item[lang]}</span></li>
))}
</ul>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="timeline">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '经历时间线' : 'Career Timeline'}</h2>
<div class="mt-5 space-y-4">
{brandTimeline.map((item) => (
<article class="rounded-md border border-border/70 p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{item.period[lang]}</p>
<h3 class="mt-1 text-base font-bold">{item.title[lang]}</h3>
<p class="mt-2 text-sm text-muted-foreground">{item.summary[lang]}</p>
{item.highlights && item.highlights.length > 0 && (
<ul class="mt-3 space-y-1.5 text-sm text-foreground/90">
{item.highlights.map((line) => <li>• {line[lang]}</li>)}
</ul>
)}
</article>
))}
</div>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="uses">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '工具与工作流' : 'Uses'}</h2>
<div class="mt-5 grid gap-5 md:grid-cols-2">
{uses.map((group) => (
<article class="rounded-md border border-border/70 p-5">
<h3 class="text-base font-bold">{group.title[lang]}</h3>
<ul class="mt-3 space-y-1.5 text-sm text-muted-foreground">
{group.items.map((item) => <li>• {item}</li>)}
</ul>
</article>
))}
</div>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="next-step">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '下一步' : 'Next Step'}</h2>
<p class="mt-3 text-muted-foreground">
{isZh
? '如果你想进一步判断合作匹配度,可以继续查看案例页与合作页。'
: 'If you want to assess collaboration fit, continue to the project cases and collaboration page.'}
</p>
<div class="mt-6 flex flex-wrap gap-3">
<a href={`${prefix}/projects`} class="inline-flex h-10 items-center rounded-lg bg-primary px-5 text-sm font-semibold text-primary-foreground">{isZh ? '查看案例' : 'View Projects'}</a>
<a href={`${prefix}/hire`} class="inline-flex h-10 items-center rounded-lg border border-border px-5 text-sm font-semibold hover:bg-muted">{isZh ? '查看合作页' : 'View Hire Page'}</a>
</div>
<div class="mt-5 border-t border-border/70 pt-4 text-sm">
<p class="font-semibold text-foreground">GitHub</p>
<a href={personalInfo.github} target="_blank" rel="noopener noreferrer" class="text-primary hover:text-primary/80 break-all">{personalInfo.github}</a>
</div>
</section>
<ContactCard lang={lang} />
</Container>
</main>
<Footer client:load lang={lang} />
<Footer lang={lang} client:load />
</Layout>

View File

@@ -86,7 +86,7 @@ Object.values(allPosts).forEach((post: any) => {
});
// Page title and description
const title = `${categoryName} - Blog | Joey Zhao`;
const title = `${categoryName} - Blog | Joey Z.`;
const description = `Explore articles about ${categoryName}. Dive into my thoughts on ${categoryName} and related topics.`;
---

View File

@@ -3,32 +3,25 @@ import BlogLayout from '../../layouts/BlogLayout.astro';
import BlogList from '../../components/blog/BlogList.astro';
import CategoryCard from '../../components/blog/CategoryCard.astro';
import TagCard from '../../components/blog/TagCard.astro';
import Container from "../../components/ui/Container.astro";
import Container from '../../components/ui/Container.astro';
import { type BlogPost } from '@/types';
import { useTranslations } from '@/i18n/utils';
import { type Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
import { sortPostsByDate } from '@/utils/blog-utils';
// Get current language environment using Astro.currentLocale
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
// Read all blog posts using import.meta.glob
const allPosts = await import.meta.glob('./posts/*.md', { eager: true });
// Process blog post data
const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => {
const slug = post.url?.split('/').filter(Boolean).pop() || '';
// Default image if not specified in frontmatter
const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center";
const defaultImage = 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center';
return {
title: post.frontmatter.title,
description: post.frontmatter.description || '',
image: post.frontmatter.image || defaultImage,
slug: slug,
slug,
tags: post.frontmatter.tags || [],
tagId: post.frontmatter.tagId || [],
category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [],
@@ -38,38 +31,29 @@ const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => {
};
});
// Sort posts by date
const sortedBlogPosts = sortPostsByDate(blogPosts);
---
<BlogLayout title="Blog - Joey Zhao" description="Thoughts on AI products, full-stack development, and building in public. Explore my latest posts below.">
<BlogLayout title="Writing - Joey Z." description="Engineering notes on architecture, delivery, and AI-assisted development workflows.">
<main class="min-h-screen">
<!-- Header Section -->
<Container className="pt-24 pb-12">
<div class="text-center mb-16">
<h1 class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-foreground via-primary to-primary dark:from-foreground dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent mb-6">
<span class="text-primary">Blog</span>
<span class="text-primary">Writing</span>
</h1>
<p class="text-xl text-muted-foreground page-content-main">
Thoughts on AI products, full-stack development, and building in public. Exploring the intersection of technology and product building.
Engineering notes on architecture, delivery, and AI-assisted development workflows.
</p>
</div>
</Container>
<!-- Main Content -->
<Container className="pb-20">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
<!-- 侧边栏 -->
<div class="lg:col-span-1 space-y-8">
<!-- 分类卡片 -->
<CategoryCard lang="en" />
<!-- 标签卡片 -->
<TagCard lang="en" />
</div>
<!-- Blog Posts -->
<div class="lg:col-span-3">
<BlogList posts={sortedBlogPosts} lang="en" />
</div>

View File

@@ -73,7 +73,7 @@ if (tagName === decodedTag) {
}
// Generate page title and description
const title = `#${tagName} - Blog | Joey Zhao`;
const title = `#${tagName} - Blog | Joey Z.`;
const description = `Explore articles tagged with #${tagName}. Dive into my thoughts on ${tagName} and related topics.`;
---

3
src/pages/contact.astro Normal file
View File

@@ -0,0 +1,3 @@
---
return Astro.redirect('/about#contact-card', 301);
---

View File

@@ -1,375 +1,105 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import { useTranslations } from "@/i18n/utils";
import { Users, Wrench, Lightbulb, Handshake, CircleDollarSign, Clock, Target, Check, Calendar, Mail, Radio, Zap, Globe, Smartphone, ShoppingCart, Layout as LayoutIcon, ClipboardList } from "lucide-react";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import ContactCard from '@/components/ContactCard.astro';
import {
collaborationModels,
collaborationFitSignals,
collaborationProcess,
collaborationFaq,
} from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
const lang = (Astro.currentLocale as Lang) || defaultLang;
const isZh = lang === 'zh';
---
<Layout title={pageTitle}>
<Layout title={isZh ? '合作' : 'Hire'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<main class="min-h-screen">
<!-- Hire Page Hero -->
<section class="py-24 relative overflow-hidden">
<div class="page-hero-overlay"></div>
<Container className="relative z-10">
<div class="text-center mb-16">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4">
{lang === 'zh' ? '专业合作' : 'Professional Collaboration'}
</span>
<h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
{lang === 'zh' ? '合作' : 'Hire Me'}
</h1>
<p class="text-lg text-muted-foreground page-content-narrow">
{lang === 'zh'
? '构建优秀产品,从找到合适的技术合伙人开始'
: 'Building great products starts with finding the right technical partner'}
</p>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main">
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '合作方式' : 'Work With Me'}</h1>
<p class="mt-4 text-lg leading-relaxed text-muted-foreground">
{isZh
? '这个页面只做一件事:帮助你快速判断我们是否匹配。'
: 'This page does one thing: help you quickly evaluate whether we are a good fit.'}
</p>
</section>
<section class="page-content-main mt-10">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '合作模型Mock' : 'Collaboration Models (Mock)'}</h2>
<div class="mt-4 grid gap-4 md:grid-cols-3">
{collaborationModels.map((model) => (
<article class="page-surface p-5">
<h3 class="text-lg font-bold">{model.title[lang]}</h3>
<p class="mt-2 text-sm text-muted-foreground">{model.description[lang]}</p>
</article>
))}
</div>
</Container>
</section>
</section>
<!-- Who I Work With -->
<section class="py-16 relative">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-primary/20 rounded-xl flex items-center justify-center">
<Users className="w-6 h-6 text-primary" />
</div>
<h2 class="text-3xl font-bold">
{lang === 'zh' ? '我与谁合作' : 'Who I Work With'}
</h2>
</div>
<section class="page-content-main mt-6 grid gap-6 md:grid-cols-2">
<article class="page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '匹配信号' : 'Good Fit Signals'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
{collaborationFitSignals.map((item) => <li>• {item[lang]}</li>)}
</ul>
</article>
<div class="grid md:grid-cols-2 gap-6">
<div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '初创公司' : 'Startups'}
</h3>
<p class="text-muted-foreground">
{lang === 'zh'
? '从零到一构建产品的早期创业团队,需要快速迭代和灵活响应的技术方案。'
: 'Early-stage startup teams building products from scratch, needing fast iteration and flexible technical solutions.'}
</p>
</div>
<article class="page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '合作流程' : 'Collaboration Process'}</h2>
<ol class="mt-4 space-y-2 text-sm text-muted-foreground list-decimal pl-5">
{collaborationProcess.map((item) => <li>{item[lang]}</li>)}
</ol>
</article>
</section>
<div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '独立创始人' : 'Indie Hackers'}
</h3>
<p class="text-muted-foreground">
{lang === 'zh'
? '独自构建产品的创业者,需要技术合伙人来实现产品愿景。'
: 'Solo founders building products who need a technical partner to realize their product vision.'}
</p>
</div>
<section class="page-content-main mt-6 page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '默认协作约定' : 'Default Working Agreement'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
<li>• {isZh ? '以清晰目标和边界为前提,不做无定义范围的推进。' : 'We start with clear outcomes and boundaries, not ambiguous execution.'}</li>
<li>• {isZh ? '同步节奏可按周设置,关键节点保持可见状态更新。' : 'Cadence is usually weekly, with visible updates at key checkpoints.'}</li>
<li>• {isZh ? '文档和决策记录优先,降低沟通成本与返工风险。' : 'Documentation and decision logs are prioritized to reduce rework.'}</li>
</ul>
</section>
<div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '传统企业' : 'Traditional Businesses'}
</h3>
<p class="text-muted-foreground">
{lang === 'zh'
? '希望数字化转型的传统企业,需要现代化的技术解决方案。'
: 'Traditional businesses looking to go digital and need modern technical solutions.'}
</p>
<section class="page-content-main mt-6 page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '常见问题Mock' : 'FAQ (Mock)'}</h2>
<div class="mt-4 space-y-4">
{collaborationFaq.map((item) => (
<div class="rounded-md border border-border/70 p-4">
<p class="text-sm font-semibold">{item.question[lang]}</p>
<p class="mt-2 text-sm text-muted-foreground">{item.answer[lang]}</p>
</div>
<div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '技术团队' : 'Technical Teams'}
</h3>
<p class="text-muted-foreground">
{lang === 'zh'
? '需要额外技术资源或特定技能集来推进项目的开发团队。'
: 'Development teams needing additional technical resources or specific skill sets to move projects forward.'}
</p>
</div>
</div>
))}
</div>
</Container>
</section>
</section>
<!-- What I Build -->
<section class="py-16 relative page-section-soft">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center">
<Wrench className="w-6 h-6 text-blue-500" />
</div>
<h2 class="text-3xl font-bold">
{lang === 'zh' ? '我能构建什么' : 'What I Build'}
</h2>
</div>
<div class="space-y-6">
<div class="page-surface p-8">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0">
<Zap className="w-6 h-6 text-white" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">AI 产品与应用</h3>
<p class="text-muted-foreground">
{lang === 'zh'
? '从 AI 助手到 Agent 系统,从 RAG 应用到 AI 驱动的 SaaS 产品。我可以帮助你将 AI 能力产品化。'
: 'From AI assistants to Agent systems, from RAG applications to AI-powered SaaS products. I can help you productize AI capabilities.'}
</p>
</div>
</div>
</div>
<div class="page-surface p-8">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-green-600 to-teal-600 rounded-xl flex items-center justify-center flex-shrink-0">
<Globe className="w-6 h-6 text-white" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">Web 应用</h3>
<p class="text-muted-foreground">
{lang === 'zh'
? '现代 Web 应用、SaaS 平台、电商系统、内容管理系统等。使用 React、Next.js、Node.js 等技术栈。'
: 'Modern web applications, SaaS platforms, e-commerce systems, content management systems, etc. Using React, Next.js, Node.js and other tech stacks.'}
</p>
</div>
</div>
</div>
<div class="page-surface p-8">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-orange-600 to-red-600 rounded-xl flex items-center justify-center flex-shrink-0">
<Smartphone className="w-6 h-6 text-white" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">全栈产品</h3>
<p class="text-muted-foreground">
{lang === 'zh'
? '从前端到后端,从数据库到部署,我提供完整的全栈开发能力,一个项目只需一个开发者。'
: 'From frontend to backend, from database to deployment, I provide complete full-stack development capabilities - one developer for the entire project.'}
</p>
</div>
</div>
</div>
</div>
<section class="page-content-main mt-6 page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '下一步' : 'Next Step'}</h2>
<p class="mt-3 text-sm text-muted-foreground">
{isZh
? '如果你觉得方向匹配,直接在本页下方联系卡片里选择你习惯的方式联系我。'
: 'If this direction fits, use the contact card below and send a short context brief.'}
</p>
<div class="mt-5 flex flex-wrap gap-3">
<a href="#contact-card" class="inline-flex h-10 items-center rounded-lg bg-primary px-5 text-sm font-semibold text-primary-foreground">
{isZh ? '查看联系卡片' : 'Open Contact Card'}
</a>
<a href={isZh ? '/zh/projects' : '/projects'} class="inline-flex h-10 items-center rounded-lg border border-border px-5 text-sm font-semibold hover:bg-muted">
{isZh ? '先看案例' : 'View Cases First'}
</a>
</div>
</Container>
</section>
</section>
<!-- Engagement Models -->
<section class="py-16 relative">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center">
<ClipboardList className="w-6 h-6 text-green-500" />
</div>
<h2 class="text-3xl font-bold">
{lang === 'zh' ? '合作模式' : 'Engagement Models'}
</h2>
</div>
<div class="grid md:grid-cols-3 gap-6">
<div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<Handshake className="w-6 h-6 text-green-600 dark:text-green-400" />
</div>
<h3 class="text-lg font-bold mb-2">
{lang === 'zh' ? '技术合伙人' : 'Technical Co-founder'}
</h3>
<p class="text-sm text-muted-foreground">
{lang === 'zh'
? '长期合作,股权合作,共同承担创业风险与回报'
: 'Long-term partnership, equity-based, sharing startup risks and rewards'}
</p>
</div>
<div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<CircleDollarSign className="w-6 h-6 text-blue-600 dark:text-blue-400" />
</div>
<h3 class="text-lg font-bold mb-2">
{lang === 'zh' ? '项目制' : 'Project-based'}
</h3>
<p class="text-sm text-muted-foreground">
{lang === 'zh'
? '按项目交付,固定价格或按阶段付款,适合明确需求的项目'
: 'Fixed-price or milestone-based, suitable for projects with clear requirements'}
</p>
</div>
<div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-primary/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<Clock className="w-6 h-6 text-primary" />
</div>
<h3 class="text-lg font-bold mb-2">
{lang === 'zh' ? '咨询/顾问' : 'Consulting'}
</h3>
<p class="text-sm text-muted-foreground">
{lang === 'zh'
? '按小时咨询,帮助你做出技术决策,审查代码,指导团队'
: 'Hourly consulting, helping you make technical decisions, code reviews, team guidance'}
</p>
</div>
</div>
</div>
</Container>
</section>
<!-- Delivery Style -->
<section class="py-16 relative page-section-soft">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-yellow-500/20 rounded-xl flex items-center justify-center">
<Target className="w-6 h-6 text-yellow-600 dark:text-yellow-400" />
</div>
<h2 class="text-3xl font-bold">
{lang === 'zh' ? '工作方式' : 'Delivery Style'}
</h2>
</div>
<div class="page-surface p-8">
<ul class="space-y-4">
<li class="flex items-start gap-3">
<Check className="w-6 h-6 text-green-500 shrink-0 mt-0.5" />
<div>
<strong class="block mb-1">
{lang === 'zh' ? '敏捷迭代' : 'Agile Iteration'}
</strong>
<p class="text-muted-foreground text-sm">
{lang === 'zh'
? '2周一个迭代快速交付可用功能持续获取反馈'
: '2-week sprints, rapid delivery of usable features, continuous feedback'}
</p>
</div>
</li>
<li class="flex items-start gap-3">
<Check className="w-6 h-6 text-green-500 shrink-0 mt-0.5" />
<div>
<strong class="block mb-1">
{lang === 'zh' ? '透明沟通' : 'Transparent Communication'}
</strong>
<p class="text-muted-foreground text-sm">
{lang === 'zh'
? '定期同步进度,及时报告问题,保持信息对称'
: 'Regular progress updates, timely issue reporting, keeping information symmetric'}
</p>
</div>
</li>
<li class="flex items-start gap-3">
<Check className="w-6 h-6 text-green-500 shrink-0 mt-0.5" />
<div>
<strong class="block mb-1">
{lang === 'zh' ? '代码质量' : 'Code Quality'}
</strong>
<p class="text-muted-foreground text-sm">
{lang === 'zh'
? '完整的测试覆盖,清晰的文档注释,可维护的代码结构'
: 'Complete test coverage, clear documentation, maintainable code structure'}
</p>
</div>
</li>
<li class="flex items-start gap-3">
<Check className="w-6 h-6 text-green-500 shrink-0 mt-0.5" />
<div>
<strong class="block mb-1">
{lang === 'zh' ? '产品思维' : 'Product Thinking'}
</strong>
<p class="text-muted-foreground text-sm">
{lang === 'zh'
? '不只是写代码,还思考产品价值,用户体验 and 业务目标'
: 'Not just writing code, but also thinking about product value, user experience, and business goals'}
</p>
</div>
</li>
</ul>
</div>
</div>
</Container>
</section>
<!-- Availability -->
<section class="py-16 relative">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-red-500/20 rounded-xl flex items-center justify-center">
<Calendar className="w-6 h-6 text-red-500" />
</div>
<h2 class="text-3xl font-bold">
{lang === 'zh' ? '当前可用性' : 'Availability'}
</h2>
</div>
<div class="page-surface p-8">
<div class="flex items-center gap-4 mb-6">
<div class="w-4 h-4 bg-green-500 rounded-full animate-pulse"></div>
<span class="text-lg font-semibold">
{lang === 'zh' ? '目前可接受新项目' : 'Currently Available for New Projects'}
</span>
</div>
<p class="text-muted-foreground mb-6">
{lang === 'zh'
? '我目前可以接受 1-2 个新项目。偏好长期合作模式(技术合伙人或长期项目),但也会考虑短期项目。'
: 'I can currently take on 1-2 new projects. Prefer long-term partnership (technical co-founder or long-term projects), but also open to short-term projects.'}
</p>
<div class="flex flex-wrap gap-2">
<span class="px-3 py-1 bg-green-500/20 text-green-600 dark:text-green-400 rounded-full text-sm">
{lang === 'zh' ? '可立即开始' : 'Available immediately'}
</span>
<span class="px-3 py-1 bg-blue-500/20 text-blue-600 dark:text-blue-400 rounded-full text-sm">
{lang === 'zh' ? '远程工作' : 'Remote only'}
</span>
<span class="px-3 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm">
{lang === 'zh' ? '中英双语' : 'Bilingual EN/ZH'}
</span>
</div>
</div>
</div>
</Container>
</section>
<!-- Contact CTA -->
<section class="py-16 relative page-section-soft">
<Container>
<div class="page-content-main text-center">
<h2 class="text-3xl font-bold mb-6">
{lang === 'zh' ? '准备好一起构建了吗?' : 'Ready to build together?'}
</h2>
<p class="text-lg text-muted-foreground mb-8 page-content-narrow">
{lang === 'zh'
? '告诉我你的项目想法,让我们看看能否合作。'
: "Tell me about your project idea and let's see if we can work together."}
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="mailto:zhaoguiyang18@gmail.com"
class="bg-primary hover:bg-primary/90 text-white px-8 py-3 rounded-lg font-semibold transition-colors inline-flex items-center justify-center gap-2"
>
<Mail className="w-5 h-5" />
{lang === 'zh' ? '发送邮件' : 'Send Email'}
</a>
<a
href={lang === 'zh' ? '/zh/now' : '/now'}
class="border border-primary text-primary hover:bg-primary hover:text-white px-8 py-3 rounded-lg font-semibold transition-colors inline-flex items-center justify-center gap-2"
>
<Radio className="w-5 h-5" />
{lang === 'zh' ? '了解更多关于我' : 'Learn More About Me'}
</a>
</div>
</div>
</Container>
</section>
<ContactCard lang={lang} showIntents={false} />
</Container>
</main>
<Footer lang={lang} client:only="react" />
<Footer lang={lang} client:load />
</Layout>

View File

@@ -1,239 +1,184 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import ProjectCard from "@/components/ProjectCard.astro";
import { useTranslations } from "@/i18n/utils";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import { personalInfo, services, projects } from "@/lib/data/index";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import {
personalInfo,
projects,
identityFacts,
heroPrinciples,
heroSkillGroups,
brandTimeline,
} from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
import { sortPostsByDate } from '@/utils/blog-utils';
const lang = (Astro.currentLocale as Lang) || defaultLang;
const t = useTranslations(lang);
const pageTitle = t("site.title");
const prefix = lang === "zh" ? "/zh" : "";
const isZh = lang === 'zh';
const prefix = isZh ? '/zh' : '';
const featuredProjects = projects[lang].filter((item) => item.featured).slice(0, 3);
const terminalFacts = identityFacts.filter((item) => item.label.en !== 'Experience');
const localizedServices = services[lang];
const featuredProjects = projects[lang].filter((project) => project.featured);
const careerMilestones = [
{
period: t("home.career.card1.period"),
title: t("home.career.card1.title"),
outcome: t("home.career.card1.outcome"),
},
{
period: t("home.career.card2.period"),
title: t("home.career.card2.title"),
outcome: t("home.career.card2.outcome"),
},
{
period: t("home.career.card3.period"),
title: t("home.career.card3.title"),
outcome: t("home.career.card3.outcome"),
},
];
const keyFacts = [
t("home.trust.item1"),
t("home.trust.item3"),
t("home.trust.item4"),
];
const postModules = await import.meta.glob('./blog/posts/*.md', { eager: true });
const latestPosts = sortPostsByDate(
Object.values(postModules).map((post: any) => ({
title: post.frontmatter.title,
description: post.frontmatter.description || '',
slug: post.url?.split('/').filter(Boolean).pop() || '',
date: post.frontmatter.date || post.frontmatter.pubDate || '',
})),
).slice(0, 3);
---
<Layout title={pageTitle}>
<GlassHeader client:load transition:persist="header" lang={lang} />
<Layout title={isZh ? '首页' : 'Home'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<main class="min-h-screen">
<div class="relative overflow-hidden">
<div class="absolute inset-0 -z-10">
<div class="absolute left-1/2 top-0 h-[600px] w-[800px] -translate-x-1/2 -translate-y-1/2 bg-primary/10 blur-[120px]"></div>
<div class="absolute right-0 top-1/4 h-[400px] w-[400px] bg-purple-500/5 blur-[100px]"></div>
<div class="absolute left-0 top-1/2 h-[400px] w-[400px] bg-orange-500/5 blur-[100px]"></div>
</div>
<section class="relative flex min-h-[80vh] items-center py-20 lg:py-28">
<Container className="relative z-10">
<div class="mx-auto max-w-5xl space-y-12">
<div class="space-y-8 text-center">
<div class="space-y-4">
<h1 class="text-5xl font-bold tracking-tight sm:text-7xl lg:text-8xl">
<span class="bg-gradient-to-b from-foreground to-foreground/70 bg-clip-text text-transparent">{personalInfo.name}</span>
</h1>
<p class="text-2xl font-medium text-foreground/80 sm:text-3xl">{personalInfo.position[lang]}</p>
</div>
<p class="mx-auto max-w-2xl text-lg leading-relaxed text-muted-foreground/90 sm:text-xl">{t("home.hero.summary")}</p>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main space-y-6">
<p class="text-sm font-semibold uppercase tracking-[0.2em] text-primary">{isZh ? 'TERMINAL PROFILE' : 'TERMINAL PROFILE'}</p>
<div class="flex flex-wrap items-center justify-center gap-4">
<a href={`${prefix}/about`} class="inline-flex h-12 items-center justify-center rounded-full bg-primary px-8 text-sm font-semibold text-primary-foreground shadow-lg shadow-primary/20 transition-all hover:translate-y-[-2px] hover:bg-primary/90 hover:shadow-xl hover:shadow-primary/30">
{t("home.hero.ctaPrimary")}
</a>
<a href={`${prefix}/hire`} class="inline-flex h-12 items-center justify-center rounded-full border border-border bg-background/50 px-8 text-sm font-semibold text-foreground backdrop-blur-sm transition-all hover:translate-y-[-2px] hover:bg-muted">
{t("home.hero.ctaSecondary")}
</a>
</div>
</div>
<div class="mx-auto max-w-3xl space-y-8">
<div class="flex flex-wrap items-center justify-center gap-3 text-sm">
{keyFacts.map((item) => (
<span class="inline-flex items-center rounded-full border border-primary/10 bg-primary/5 px-4 py-1.5 font-medium text-primary/80 backdrop-blur-sm">
<span class="mr-2 h-1 w-1 rounded-full bg-primary/50"></span>
{item}
</span>
))}
</div>
<div class="group relative">
<div class="absolute -inset-1 rounded-2xl bg-gradient-to-r from-primary/20 to-purple-500/20 opacity-50 blur transition duration-1000 group-hover:opacity-100 group-hover:duration-200"></div>
<div class="page-surface relative overflow-hidden rounded-2xl border bg-card/40 backdrop-blur-md">
<div class="flex items-center justify-between border-b border-border/50 bg-muted/20 px-5 py-3">
<div class="flex items-center gap-2">
<span class="h-3 w-3 rounded-full bg-red-400/80 shadow-[0_0_8px_rgba(248,113,113,0.4)]"></span>
<span class="h-3 w-3 rounded-full bg-yellow-400/80 shadow-[0_0_8px_rgba(250,204,21,0.4)]"></span>
<span class="h-3 w-3 rounded-full bg-green-400/80 shadow-[0_0_8px_rgba(74,222,128,0.4)]"></span>
</div>
<span class="font-mono text-xs font-medium text-muted-foreground/70 tracking-tight">{personalInfo.terminal.username}</span>
</div>
<div class="space-y-3 p-6 font-mono text-sm leading-relaxed text-foreground/90">
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">whoami</span> {personalInfo.name}</p>
</div>
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">role</span> {personalInfo.position[lang]}</p>
</div>
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">stack</span> TypeScript, React, Node.js, Astro</p>
</div>
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">status</span> {t("home.hero.terminalStatus")}</p>
</div>
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">contact</span> {personalInfo.github}</p>
</div>
</div>
</div>
</div>
</div>
<div class="space-y-3">
<h1 class="text-4xl font-bold tracking-tight text-foreground sm:text-5xl lg:text-6xl">{personalInfo.name}</h1>
<p class="text-lg font-medium text-primary/90">{personalInfo.position[lang]}</p>
</div>
</Container>
</section>
<div class="space-y-24 pb-24">
<section class="relative">
<div class="py-24 lg:py-32">
<Container>
<div class="mb-16 flex flex-col gap-6 md:flex-row md:items-end md:justify-between">
<div class="space-y-4">
<h2 class="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">{t("home.services.title")}</h2>
<p class="max-w-2xl text-lg leading-relaxed text-muted-foreground/90">{t("home.services.description")}</p>
</div>
<a href={`${prefix}/services`} class="inline-flex items-center text-sm font-bold text-primary transition-colors hover:text-primary/70">
{t("services.viewAll")}
<svg class="ml-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"/></svg>
</a>
<div class="space-y-3 text-xs leading-relaxed sm:text-sm">
{heroPrinciples.map((line, index) => (
<p
class={
index === 0
? 'inline-flex items-center rounded-md border border-primary/35 bg-primary/10 px-3 py-1.5 font-semibold text-primary'
: 'text-foreground/80'
}
>
{index === 0 && <span class="mr-2 text-[10px] uppercase tracking-wider text-primary/80">{isZh ? 'Now Hiring' : 'Open to Work'}</span>}
{line[lang]}
</p>
))}
</div>
<article class="page-surface overflow-hidden border border-border/80">
<div class="flex items-center justify-between border-b border-border/80 px-4 py-2">
<div class="flex items-center gap-2">
<span class="h-2.5 w-2.5 rounded-full bg-red-400"></span>
<span class="h-2.5 w-2.5 rounded-full bg-yellow-400"></span>
<span class="h-2.5 w-2.5 rounded-full bg-green-400"></span>
</div>
<p class="text-xs font-mono text-muted-foreground">joey@engineer:~</p>
</div>
<div class="space-y-6 p-5 font-mono text-sm">
<div class="space-y-2">
<p><span class="text-primary">$</span> whoami</p>
<p class="pl-4 text-foreground">{personalInfo.name}</p>
</div>
<div class="grid gap-10 md:grid-cols-2">
{localizedServices.map((service) => (
<article class="group relative rounded-2xl p-6 transition-all duration-500 hover:bg-card/50 hover:shadow-xl hover:shadow-primary/5">
<div class="mb-8 flex items-center gap-6">
<div class={`flex h-14 w-14 items-center justify-center rounded-2xl bg-gradient-to-br ${service.icon.gradient} text-white shadow-lg transition-transform duration-500 group-hover:scale-110 group-hover:rotate-3`}>
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<Fragment set:html={service.icon.svg} />
</svg>
<div class="space-y-2">
<p><span class="text-primary">$</span> profile --summary</p>
<div class="grid gap-2 pl-4 text-muted-foreground sm:grid-cols-2">
{terminalFacts.map((fact) => (
<p><span class="text-foreground/90">{fact.label[lang]}:</span> {fact.value[lang]}</p>
))}
</div>
</div>
<div class="space-y-3">
<p><span class="text-primary">$</span> stack --list</p>
<div class="space-y-3 pl-4">
{heroSkillGroups.map((group) => (
<div>
<p class="text-xs uppercase tracking-wide text-primary/80">{group.name[lang]}</p>
<div class="mt-2 flex flex-wrap gap-2">
{group.items.map((item) => (
<span class="rounded-md border border-border/80 bg-muted/40 px-2 py-1 text-xs text-foreground/90">{item}</span>
))}
</div>
<h3 class="text-2xl font-bold tracking-tight">{service.title}</h3>
</div>
<ul class="space-y-4">
{service.items.slice(0, 3).map((item) => (
<li class="flex items-start gap-3 text-muted-foreground group-hover:text-foreground/90 transition-colors">
<span class="mt-2.5 h-1 w-1 shrink-0 rounded-full bg-primary/30 group-hover:bg-primary transition-all"></span>
<span class="text-base leading-relaxed">{item}</span>
</li>
))}
))}
</div>
</div>
</div>
</article>
</section>
<section class="page-content-main mt-20">
<div class="mb-8 flex items-end justify-between gap-4">
<h2 class="text-2xl font-bold tracking-tight sm:text-3xl">{isZh ? '经历速览' : 'Career Snapshot'}</h2>
<a href={`${prefix}/about#timeline`} class="text-sm font-semibold text-primary hover:text-primary/80">{isZh ? '查看完整经历' : 'View Full Timeline'}</a>
</div>
<div class="relative pl-6">
<div class="absolute left-[0.7rem] top-2 h-[calc(100%-0.5rem)] w-px bg-border"></div>
<div class="space-y-6">
{brandTimeline.map((item) => (
<article class="relative rounded-lg border border-border/70 bg-card/60 p-5">
<span class="absolute -left-[1.06rem] top-6 h-3 w-3 rounded-full border-2 border-primary bg-background"></span>
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{item.period[lang]}</p>
<h3 class="mt-2 text-base font-bold leading-snug">{item.title[lang]}</h3>
<p class="mt-2 text-sm text-muted-foreground">{item.summary[lang]}</p>
{item.highlights && item.highlights.length > 0 && (
<ul class="mt-3 space-y-1.5 text-sm text-foreground/90">
{item.highlights.map((line) => <li>• {line[lang]}</li>)}
</ul>
</article>
))}
</div>
</Container>
)}
</article>
))}
</div>
</div>
</section>
<section class="relative">
<Container>
<div class="mb-16 space-y-3">
<h2 class="text-4xl font-bold tracking-tight sm:text-5xl">{t("home.career.title")}</h2>
<p class="max-w-xl text-lg text-muted-foreground">{t("home.career.description")}</p>
</div>
<section class="page-content-main mt-20">
<div class="mb-8 flex items-end justify-between gap-4">
<h2 class="text-2xl font-bold tracking-tight sm:text-3xl">{isZh ? '项目经历' : 'Project Experience'}</h2>
<a href={`${prefix}/projects`} class="text-sm font-semibold text-primary hover:text-primary/80">{isZh ? '查看全部项目' : 'View All Projects'}</a>
</div>
<div class="relative mx-auto max-w-4xl">
<div class="absolute left-0 top-0 h-full w-px bg-gradient-to-b from-primary/50 via-primary/10 to-transparent sm:left-1/2"></div>
<div class="space-y-12">
{careerMilestones.map((item, index) => (
<div class={`relative flex flex-col sm:flex-row ${index % 2 === 0 ? 'sm:flex-row-reverse' : ''}`}>
<div class="absolute -left-1.5 top-2 h-3 w-3 rounded-full border-2 border-primary bg-background sm:left-1/2 sm:-ml-1.5"></div>
<div class="w-full pl-8 sm:w-1/2 sm:px-12">
<article class="rounded-2xl p-4 transition-all hover:bg-card/40">
<span class="text-xs font-bold uppercase tracking-wider text-primary/60">{item.period}</span>
<h3 class="mt-2 text-xl font-bold leading-tight">{item.title}</h3>
<p class="mt-3 text-sm leading-relaxed text-muted-foreground/90">{item.outcome}</p>
</article>
</div>
</div>
))}
</div>
</div>
<div class="mt-16 text-center">
<a href={`${prefix}/about`} class="inline-flex h-12 items-center rounded-full border border-border bg-background px-8 text-sm font-semibold transition-all hover:bg-muted">
{t("home.career.cta")}
</a>
</div>
</Container>
</section>
<section class="relative">
<div class="py-24 lg:py-32">
<Container>
<div class="mb-12 space-y-3">
<h2 class="text-4xl font-bold tracking-tight sm:text-5xl">{t("home.featured.title")}</h2>
</div>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{featuredProjects.map((project) => (
<ProjectCard project={project} lang={lang} />
))}
</div>
</Container>
<div class="grid gap-6 lg:grid-cols-3">
{featuredProjects.map((project) => (
<article class="page-surface p-6">
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{project.systemType}</p>
<h3 class="mt-2 text-lg font-bold">{project.title}</h3>
<p class="mt-3 text-sm leading-relaxed text-muted-foreground">{project.context}</p>
<p class="mt-4 text-xs font-semibold uppercase tracking-wide text-foreground/70">{isZh ? '结果' : 'Outcome'}</p>
<p class="mt-1 text-sm text-foreground/90">{project.outcomes?.[0] ?? project.impact}</p>
</article>
))}
</div>
</section>
<section class="relative">
<Container>
<div class="flex flex-col items-center text-center">
<h2 class="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl mb-6">{t("home.final.title")}</h2>
<p class="text-lg leading-relaxed text-muted-foreground/90 sm:text-xl max-w-2xl mb-10">{t("home.final.description")}</p>
<div class="flex flex-wrap justify-center gap-4">
<a href={`${prefix}/about`} class="inline-flex h-12 items-center justify-center rounded-full bg-primary px-8 text-sm font-semibold text-primary-foreground transition-all hover:translate-y-[-2px] hover:bg-primary/90">
{t("home.final.ctaPrimary")}
</a>
<a href={`${prefix}/hire`} class="inline-flex h-12 items-center justify-center rounded-full border border-border px-8 text-sm font-semibold text-foreground transition-all hover:translate-y-[-2px] hover:bg-muted">
{t("home.final.ctaSecondary")}
</a>
</div>
</div>
</Container>
</section>
</div>
</div>
</main>
<section class="page-content-main mt-20">
<div class="mb-8 flex items-end justify-between gap-4">
<h2 class="text-2xl font-bold tracking-tight sm:text-3xl">{isZh ? '最新写作' : 'Latest Writing'}</h2>
<a href={`${prefix}/blog`} class="text-sm font-semibold text-primary hover:text-primary/80">{isZh ? '查看全部文章' : 'View All Writing'}</a>
</div>
<Footer lang={lang} client:only="react" />
<div class="grid gap-4 md:grid-cols-3">
{latestPosts.map((post) => (
<article class="page-surface p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{post.date}</p>
<h3 class="mt-2 text-base font-bold leading-snug">
<a href={`${prefix}/blog/posts/${post.slug}`} class="hover:text-primary">{post.title}</a>
</h3>
<p class="mt-2 line-clamp-3 text-sm text-muted-foreground">{post.description}</p>
</article>
))}
</div>
</section>
<section class="page-content-main mt-20 page-surface p-8">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '关于合作' : 'About Collaboration'}</h2>
<div class="mt-6 flex flex-wrap gap-3">
<a href={`${prefix}/about`} class="inline-flex h-10 items-center rounded-lg bg-primary px-5 text-sm font-semibold text-primary-foreground">{isZh ? '查看完整经历' : 'Read Full Profile'}</a>
<a href={`${prefix}/hire#contact-card`} class="inline-flex h-10 items-center rounded-lg border border-border px-5 text-sm font-semibold hover:bg-muted">{isZh ? '合作与联系' : 'Collaborate & Contact'}</a>
</div>
<p class="mt-4 text-xs text-muted-foreground">{personalInfo.name} · {personalInfo.position[lang]} · {personalInfo.location}</p>
</section>
</Container>
</main>
<Footer lang={lang} client:load />
</Layout>

View File

@@ -1,228 +1,67 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import { useTranslations } from "@/i18n/utils";
import { Hammer, Zap, Telescope, Bot, Target, Rocket, Sparkles, MessageSquare, Handshake, MapPin } from "lucide-react";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = lang === 'zh' ? '现在' : 'Now';
const lang = (Astro.currentLocale as Lang) || defaultLang;
const isZh = lang === 'zh';
const doing = isZh
? ['推进工程化案例化的个人站升级', '优化远程协作下的交付流程与文档标准', '持续打磨 AI 协作开发实践']
: ['Upgrading this portfolio into an engineering case-study site', 'Improving remote-first delivery workflow and documentation quality', 'Refining practical AI-assisted development workflows'];
const exploring = isZh
? ['复杂权限系统的前端边界设计', '大规模业务表单与状态流管理', '更稳定的跨团队异步协作机制']
: ['Frontend boundary design for complex permission systems', 'Scalable state management for large business forms', 'More stable async collaboration practices across teams'];
const shipping = isZh
? ['工程案例库结构重构', '导航与路由转化路径优化', '双语文案统一与信息层级简化']
: ['Refactoring project pages into engineering case studies', 'Optimizing navigation and conversion routes', 'Unifying EN/ZH copy and simplifying information hierarchy'];
---
<Layout title={pageTitle}>
<Layout title={isZh ? '现在' : 'Now'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<main class="min-h-screen relative overflow-hidden">
<!-- 背景装饰 -->
<div class="absolute inset-0 -z-10">
<div class="absolute left-1/2 top-0 h-[800px] w-[1000px] -translate-x-1/2 -translate-y-1/2 bg-primary/10 blur-[120px]"></div>
<div class="absolute right-0 top-1/3 h-[500px] w-[500px] bg-blue-500/5 blur-[100px]"></div>
</div>
<!-- Now Page Hero -->
<section class="pt-32 pb-24 relative overflow-hidden">
<div class="page-hero-overlay" aria-hidden="true"></div>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main">
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '现在' : 'Now'}</h1>
<p class="mt-4 text-lg text-muted-foreground">{isZh ? '我当前的工作重点与近期交付。' : 'What I am focusing on right now and what I am shipping recently.'}</p>
</section>
<Container className="relative z-10">
<div class="max-w-4xl mx-auto text-center">
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-6 animate-fade-in">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
</span>
{lang === 'zh' ? '实时状态' : "Live Status"}
</div>
<h1 class="page-title-gradient text-6xl md:text-8xl font-bold mb-8 tracking-tight">
{lang === 'zh' ? '现在' : 'Now'}
</h1>
<p class="text-xl md:text-2xl text-muted-foreground leading-relaxed max-w-2xl mx-auto">
{lang === 'zh'
? '关于我现在正在关注的事、正在构建的产品以及我的生活状态。'
: 'What I am currently focusing on, the products I am building, and my life status.'}
</p>
</div>
</Container>
</section>
<section class="page-content-main mt-10 grid gap-6 md:grid-cols-3">
<article class="page-surface p-6">
<h2 class="text-lg font-bold">{isZh ? '在做什么' : 'Doing'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
{doing.map((item) => <li>• {item}</li>)}
</ul>
</article>
<!-- Main Content -->
<section class="pb-32 relative mt-12 md:mt-24">
<Container>
<div class="max-w-4xl mx-auto space-y-24">
<!-- Building Section -->
<div class="space-y-10">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-primary/10 rounded-xl flex items-center justify-center border border-primary/20">
<Hammer className="w-6 h-6 text-primary" />
</div>
<h2 class="text-3xl md:text-4xl font-bold tracking-tight">
{lang === 'zh' ? '正在构建' : 'Building'}
</h2>
</div>
<article class="page-surface p-6">
<h2 class="text-lg font-bold">{isZh ? '在研究什么' : 'Exploring'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
{exploring.map((item) => <li>• {item}</li>)}
</ul>
</article>
<div class="group relative">
<div class="absolute -inset-1 rounded-[2rem] bg-gradient-to-r from-primary/30 to-blue-600/30 opacity-20 blur-xl transition duration-500 group-hover:opacity-40"></div>
<div class="page-surface relative p-8 md:p-12 overflow-hidden border-primary/10 rounded-[2rem]">
<div class="flex flex-col md:flex-row gap-10 items-start">
<div class="w-24 h-24 bg-gradient-to-br from-primary to-blue-600 rounded-2xl flex items-center justify-center flex-shrink-0 shadow-lg shadow-primary/20 group-hover:scale-105 transition-transform duration-500">
<Zap className="w-12 h-12 text-white" />
</div>
<div class="flex-1 space-y-6">
<div class="flex items-center justify-between">
<h3 class="text-3xl font-bold">Elynd</h3>
<span class="px-4 py-1.5 bg-green-500/10 text-green-600 dark:text-green-400 rounded-full text-xs font-bold border border-green-500/20 uppercase tracking-wider">Active</span>
</div>
<p class="text-xl text-muted-foreground leading-relaxed">
{lang === 'zh'
? '一个开放的 AI 工作空间,让构建者能够更智能地工作。从 AI 第一性原理出发,打造完全开放、可自托管的协作工具。'
: 'An open AI workspace for builders to work smarter. Built from first principles of AI, creating fully open, self-hostable collaboration tools.'}
</p>
<div class="flex flex-wrap gap-3 pt-2">
{['AI Product', 'TypeScript', 'Open Source', 'Astro'].map(tag => (
<span class="px-4 py-2 bg-secondary/50 text-secondary-foreground rounded-lg text-sm font-medium border border-border/50">{tag}</span>
))}
</div>
</div>
</div>
</div>
</div>
</div>
<article class="page-surface p-6">
<h2 class="text-lg font-bold">{isZh ? '最近交付' : 'Shipping'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
{shipping.map((item) => <li>• {item}</li>)}
</ul>
</article>
</section>
<!-- Exploring Section -->
<div class="space-y-10">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-blue-500/10 rounded-xl flex items-center justify-center border border-blue-500/20">
<Telescope className="w-6 h-6 text-blue-500" />
</div>
<h2 class="text-3xl md:text-4xl font-bold tracking-tight">
{lang === 'zh' ? '正在探索' : 'Exploring'}
</h2>
</div>
<div class="grid gap-8 md:grid-cols-2">
{[
{
title: lang === 'zh' ? 'AI Agents 与工作流自动化' : 'AI Agents & Workflow Automation',
desc: lang === 'zh' ? '研究如何构建能够自主完成复杂任务的 AI Agent以及如何将 AI 能力融入日常工作流。' : 'Researching how to build AI Agents that can autonomously complete complex tasks, and how to integrate AI capabilities into daily workflows.',
icon: Bot
},
{
title: lang === 'zh' ? '产品导向的开发实践' : 'Product-Led Development Practices',
desc: lang === 'zh' ? '探索从产品角度思考开发,构建真正解决用户问题的产品,而不仅仅是技术实现。' : 'Exploring product thinking in development, building products that truly solve user problems, not just technical implementations.',
icon: Target
}
].map(item => (
<div class="page-surface p-8 rounded-3xl hover:translate-y-[-4px] transition-all duration-300 border-border/50 group">
<div class="w-12 h-12 bg-muted rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300">
<item.icon className="w-6 h-6 text-foreground" />
</div>
<h3 class="text-2xl font-bold mb-4">{item.title}</h3>
<p class="text-muted-foreground text-base leading-relaxed">{item.desc}</p>
</div>
))}
</div>
</div>
<!-- Shipping Section -->
<div class="space-y-10">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-green-500/10 rounded-xl flex items-center justify-center border border-green-500/20">
<Rocket className="w-6 h-6 text-green-500" />
</div>
<h2 class="text-3xl md:text-4xl font-bold tracking-tight">
{lang === 'zh' ? '最近发布' : 'Shipping'}
</h2>
</div>
<div class="space-y-6">
{[
{
title: lang === 'zh' ? '个人网站全新改版' : 'Portfolio Redesign',
desc: lang === 'zh' ? '基于 Astro 5 和 Tailwind 4 的高性能双语站点。' : 'High-performance bilingual site built with Astro 5 & Tailwind 4.',
icon: Sparkles,
color: 'bg-green-500/10',
iconColor: 'text-green-500'
},
{
title: lang === 'zh' ? 'DeepSeek 满血版接入' : 'DeepSeek Full Access',
desc: lang === 'zh' ? '在本地工作流中深度整合 DeepSeek-V3 提升效率。' : 'Deep integration of DeepSeek-V3 in local workflows for efficiency.',
icon: MessageSquare,
color: 'bg-blue-500/10',
iconColor: 'text-blue-500'
}
].map(item => (
<div class="group relative flex items-center gap-6 p-4 rounded-2xl hover:bg-muted/50 transition-all duration-300">
<div class={`w-12 h-12 ${item.color} rounded-xl flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform`}>
<item.icon className={`w-6 h-6 ${item.iconColor}`} />
</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-lg group-hover:text-primary transition-colors truncate">
{item.title}
</h3>
<p class="text-sm text-muted-foreground line-clamp-1">
{item.desc}
</p>
</div>
<div class="text-muted-foreground/30 group-hover:text-primary/50 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</div>
</div>
))}
</div>
</div>
<!-- Bottom Grid: Collaboration & Meta -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 pt-8">
<!-- Collaboration Card -->
<div class="relative group h-full">
<div class="absolute -inset-1 rounded-[2.5rem] bg-gradient-to-br from-yellow-500/20 to-orange-500/20 blur opacity-75"></div>
<div class="page-surface relative p-10 border-yellow-500/10 bg-gradient-to-br from-card to-yellow-500/5 rounded-[2.5rem] h-full flex flex-col justify-between">
<div>
<div class="w-14 h-14 bg-yellow-500/10 rounded-2xl flex items-center justify-center mb-8">
<Handshake className="w-8 h-8 text-yellow-600 dark:text-yellow-400" />
</div>
<h3 class="text-3xl font-bold mb-4">{lang === 'zh' ? '开放合作' : 'Collaborate'}</h3>
<p class="text-lg text-muted-foreground mb-8 leading-relaxed">
{lang === 'zh'
? '始终对有趣的产品想法和技术咨询持开放态度。'
: "Always open to interesting product ideas and technical consulting."}
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<a href={lang === 'zh' ? '/zh/hire' : '/hire'} class="flex items-center justify-center py-4 rounded-2xl bg-primary text-primary-foreground font-bold text-base hover:scale-[1.02] transition-transform">
{lang === 'zh' ? '合作方式' : 'View Options'}
</a>
<a href="mailto:zhaoguiyang18@gmail.com" class="flex items-center justify-center py-4 rounded-2xl border border-border bg-background/50 text-foreground font-bold text-base hover:bg-muted transition-colors">
{lang === 'zh' ? '发送邮件' : 'Email Me'}
</a>
</div>
</div>
</div>
<!-- Meta/Status Card -->
<div class="page-surface p-10 border-border/50 rounded-[2.5rem] flex flex-col items-center justify-center text-center space-y-6">
<div class="w-20 h-20 bg-muted rounded-full flex items-center justify-center text-3xl">
<MapPin className="w-10 h-10 text-primary" />
</div>
<div class="space-y-2">
<h3 class="text-2xl font-bold">{lang === 'zh' ? '当前位置' : 'Location'}</h3>
<p class="text-muted-foreground">{lang === 'zh' ? '中国 · 杭州' : 'Hangzhou, China'}</p>
</div>
<div class="pt-4 w-full">
<div class="inline-flex items-center gap-3 px-5 py-2.5 rounded-full bg-muted/50 border border-border/50 mx-auto">
<span class="text-xs text-muted-foreground uppercase font-bold tracking-widest border-r border-border/50 pr-3">
{lang === 'zh' ? '最后更新' : 'Updated'}
</span>
<span class="text-sm font-semibold">2025.03.14</span>
</div>
</div>
</div>
</div>
</div>
</Container>
</section>
<section class="page-content-main mt-8 page-surface p-6">
<p class="text-sm text-muted-foreground">
{isZh ? '最后更新2026-03-16' : 'Last updated: 2026-03-16'}
</p>
</section>
</Container>
</main>
<Footer lang={lang} client:only="react" />
<Footer lang={lang} client:load />
</Layout>

View File

@@ -1,114 +1,111 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import ProjectCard from "@/components/ProjectCard.astro";
import { useTranslations } from "@/i18n/utils";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import { projects } from "@/lib/data/index";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import { projects } from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = t('projects.title');
// 根据当前语言获取项目数据
const currentProjects = projects[lang as keyof typeof projects] || projects.en;
const filterOptions = [
{ key: "all", label: t("project.filter.all") },
{ key: "product", label: t("project.type.product") },
{ key: "client", label: t("project.type.client") },
{ key: "experiment", label: t("project.type.experiment") },
];
const lang = (Astro.currentLocale as Lang) || defaultLang;
const isZh = lang === 'zh';
const pageProjects = projects[lang].map((project) => ({
...project,
category: project.id === 'elynd' ? 'indie' : 'enterprise',
}));
const enterpriseProjects = pageProjects.filter((project) => project.category === 'enterprise');
const indieProjects = pageProjects.filter((project) => project.category === 'indie');
---
<Layout title={pageTitle}>
<Layout title={isZh ? '项目' : 'Projects'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<main class="min-h-screen relative overflow-hidden">
<div
aria-hidden="true"
class="page-hero-overlay"
></div>
<section class="relative z-10 py-24">
<Container>
<div class="text-center mb-8">
<h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
{t('projects.title')}
</h1>
<p class="text-lg text-muted-foreground page-content-main mb-12 leading-relaxed">
{t('projects.slogan')} {t('projects.description')}
</p>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main">
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '项目经历' : 'Project Experience'}</h1>
<p class="mt-4 text-lg leading-relaxed text-muted-foreground">
{isZh
? '按两类展示:企业项目与独立开发项目。采用卡片布局,聚焦项目背景、职责和结果。'
: 'Organized into two categories: enterprise projects and independent projects, shown in a cover-style card grid.'}
</p>
</section>
<section class="page-content-main mt-8">
<div class="mb-5 flex items-end justify-between gap-3">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '企业项目' : 'Enterprise Projects'}</h2>
<span class="text-sm text-muted-foreground">{enterpriseProjects.length} {isZh ? '个项目' : 'projects'}</span>
</div>
</Container>
</section>
<section class="relative z-10 pb-20" data-projects-root>
<Container>
<div class="page-content-main">
<div class="page-surface flex flex-wrap gap-2 mb-8 p-1 rounded-xl">
{filterOptions.map((option, index) => (
<button
type="button"
data-filter={option.key}
aria-pressed={index === 0 ? "true" : "false"}
class={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
index === 0
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground hover:bg-muted"
}`}
>
{option.label}
</button>
))}
</div>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{currentProjects.map((project) => (
<ProjectCard project={project} lang={lang} showStatus={true} />
))}
</div>
<div class="grid gap-5 sm:grid-cols-2 xl:grid-cols-3">
{enterpriseProjects.map((project) => (
<article class="page-surface overflow-hidden">
<div class={`bg-gradient-to-br ${project.image.bg} p-5`}>
<p class="text-xs font-semibold uppercase tracking-wider text-foreground/80">{isZh ? '企业项目' : 'Enterprise'}</p>
<h3 class="mt-2 text-lg font-bold">{project.title}</h3>
<p class="mt-2 text-sm text-foreground/80">{project.systemType}</p>
</div>
<div class="p-5">
<p class="text-sm text-muted-foreground">{project.context}</p>
<div class="mt-4 flex flex-wrap gap-2">
{project.tech.slice(0, 5).map((tech) => (
<span class="rounded-md border border-border bg-muted/50 px-2.5 py-1 text-xs font-medium">{tech}</span>
))}
</div>
<p class="mt-4 text-sm font-semibold text-foreground/90">{isZh ? '结果:' : 'Outcome:'} {project.outcomes?.[0] ?? project.impact}</p>
<p class="mt-3 text-xs text-muted-foreground">{isZh ? '受保密协议限制,暂不提供在线预览。' : 'Online preview is not available due to confidentiality restrictions.'}</p>
</div>
</article>
))}
</div>
</Container>
</section>
</section>
<section class="page-content-main mt-12">
<div class="mb-5 flex items-end justify-between gap-3">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '独立项目' : 'Independent Projects'}</h2>
<span class="text-sm text-muted-foreground">{indieProjects.length} {isZh ? '个项目' : 'projects'}</span>
</div>
<div class="grid gap-5 sm:grid-cols-2 xl:grid-cols-3">
{indieProjects.map((project) => (
<article class="page-surface overflow-hidden">
<div class="relative aspect-[16/9] overflow-hidden border-b border-border/70 bg-muted/30">
{project.coverImage ? (
<img
src={project.coverImage}
alt={project.coverImageAlt || project.title}
class="h-full w-full object-cover"
loading="lazy"
decoding="async"
/>
) : (
<div class={`flex h-full w-full flex-col justify-end bg-gradient-to-br ${project.image.bg} p-5`}>
<p class="text-xs font-semibold uppercase tracking-wider text-foreground/80">{isZh ? '封面待补充' : 'Cover Coming Soon'}</p>
<h3 class="mt-2 text-lg font-bold">{project.title}</h3>
<p class="mt-1 text-sm text-foreground/80">{project.systemType}</p>
</div>
)}
</div>
<div class="p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{isZh ? '独立项目' : 'Independent'}</p>
<h3 class="mt-2 text-lg font-bold">{project.title}</h3>
<p class="text-sm text-muted-foreground">{project.context}</p>
<div class="mt-4 flex flex-wrap gap-2">
{project.tech.slice(0, 5).map((tech) => (
<span class="rounded-md border border-border bg-muted/50 px-2.5 py-1 text-xs font-medium">{tech}</span>
))}
</div>
<p class="mt-4 text-sm font-semibold text-foreground/90">{isZh ? '结果:' : 'Outcome:'} {project.outcomes?.[0] ?? project.impact}</p>
{project.link !== '#' && (
<a href={project.link} target="_blank" rel="noopener noreferrer" class="mt-3 inline-flex text-sm font-semibold text-primary hover:text-primary/80">
{isZh ? '查看项目' : 'Open Project'}
</a>
)}
</div>
</article>
))}
</div>
</section>
</Container>
</main>
<Footer lang={lang} client:load />
</Layout>
<script>
const projectsRoot = document.querySelector("[data-projects-root]");
if (projectsRoot) {
const filterButtons = Array.from(projectsRoot.querySelectorAll("[data-filter]"));
const projectCards = Array.from(projectsRoot.querySelectorAll("[data-project-card]"));
const applyFilter = (activeFilter: string | null) => {
projectCards.forEach((card) => {
const projectType = card.getAttribute("data-type");
const shouldShow = activeFilter === "all" || projectType === activeFilter;
card.classList.toggle("hidden", !shouldShow);
});
filterButtons.forEach((button) => {
const isActive = button.getAttribute("data-filter") === activeFilter;
button.setAttribute("aria-pressed", isActive ? "true" : "false");
button.classList.toggle("bg-primary", isActive);
button.classList.toggle("text-primary-foreground", isActive);
button.classList.toggle("text-muted-foreground", !isActive);
button.classList.toggle("hover:text-foreground", !isActive);
button.classList.toggle("hover:bg-muted", !isActive);
});
};
filterButtons.forEach((button) => {
button.addEventListener("click", () => {
const selectedFilter = button.getAttribute("data-filter") ?? "all";
applyFilter(selectedFilter);
});
});
applyFilter("all");
}
</script>

View File

@@ -1,42 +1,14 @@
---
title: "What I Do"
description: "Professional services focused on building AI products and technical excellence"
title: "Moved to Uses"
description: "This page has moved."
layout: "../layouts/AboutLayout.astro"
---
import HighlightBox from '../components/markdown/HighlightBox.astro';
# This page moved
# What I Do 🚀
The old **Services** page has been replaced by focused pages:
<HighlightBox type="tip">
Building AI-powered products and turning ambitious ideas into reality.
</HighlightBox>
- [Uses](/uses)
- [About (Contact Card)](/about#contact-card)
## AI Product Development 🤖
- Building AI-powered products from concept to launch
- RAG applications, AI agents, and intelligent automation
- Full-stack development with modern tech stack (React, Next.js, Node.js)
- Product thinking - focus on solving real problems
- Rapid prototyping and iterative development
## Technical Consulting 📊
<HighlightBox type="info" title="How I Can Help">
- Technical architecture and stack decisions
- Code reviews and best practices implementation
- Team mentoring and knowledge transfer
- Performance optimization and scaling strategies
- Technical due diligence for investments
</HighlightBox>
# Let's Build Together 💼
<HighlightBox type="success" title="Get in Touch">
I'm always open to discussing new projects and opportunities. Whether you're looking for a technical co-founder, need help building an AI product, or want technical consulting - let's talk.
- Email: **zhaoguiyang18@gmail.com**
- Check out my [Hire Me](/hire) page for collaboration options
</HighlightBox>
If you are hiring for a remote role, please start from [Hire](/hire) and use the contact card on that page.

3
src/pages/uses.astro Normal file
View File

@@ -0,0 +1,3 @@
---
return Astro.redirect('/about#uses', 301);
---

View File

@@ -1,220 +1,127 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import HighlightBox from "@/components/markdown/HighlightBox.astro";
import { useTranslations } from "@/i18n/utils";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import {
Sparkles,
Heart,
Zap,
Mail,
MessageSquare,
Send,
Github,
Twitter,
Linkedin,
Globe
} from "lucide-react";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import ContactCard from '@/components/ContactCard.astro';
import {
personalInfo,
uses,
aboutNarrative,
aboutCapabilities,
aboutPrinciples,
brandTimeline,
} from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
const IconMap: Record<string, any> = {
mail: Mail,
wechat: MessageSquare,
qq: MessageSquare, // Lucide doesn't have QQ, using MessageSquare
send: Send,
github: Github,
twitter: Twitter,
linkedin: Linkedin,
globe: Globe,
};
const lang = (Astro.currentLocale as Lang) || 'zh';
const t = useTranslations(lang);
const pageTitle = t("about.title");
const lang = (Astro.currentLocale as Lang) || defaultLang;
const isZh = lang === 'zh';
const prefix = isZh ? '/zh' : '';
---
<Layout title={pageTitle}>
<GlassHeader client:load transition:persist="header" lang={lang} />
<main class="min-h-screen relative overflow-hidden pt-16 sm:pt-20">
<!-- Background Decor -->
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
<div class="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 dark:from-primary/10 dark:via-transparent dark:to-primary/5"></div>
</div>
<Layout title={isZh ? '关于' : 'About'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<!-- Hero Section -->
<section class="relative z-10 py-16 md:py-24">
<Container>
<div class="max-w-4xl mx-auto">
<div class="flex flex-col md:flex-row items-center gap-12 mb-16">
<div class="relative group">
<div class="absolute -inset-1 rounded-full bg-gradient-to-r from-primary to-purple-600 opacity-75 blur transition duration-1000 group-hover:opacity-100 group-hover:duration-200"></div>
<div class="relative h-48 w-48 rounded-full border-4 border-background overflow-hidden bg-muted">
<img
src="/avatar.png"
alt="Joey Zhao"
class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110"
onerror="this.src='https://ui-avatars.com/api/?name=Joy+Zhao&background=0D8ABC&color=fff&size=200'"
/>
</div>
</div>
<div class="text-center md:text-left space-y-6">
<h1 class="text-4xl md:text-6xl font-bold tracking-tight">
<span class="bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent">
{t('about.title')}
</span>
</h1>
<p class="text-xl text-muted-foreground leading-relaxed">
{t('about.description')}
</p>
</div>
</div>
<div class="grid gap-8">
<!-- Intro Box -->
<HighlightBox type="tip" title={t('about.intro.title')}>
<div class="space-y-4 py-2">
<p class="text-lg leading-relaxed whitespace-pre-line">{t('about.intro.content')}</p>
<div class="p-4 rounded-xl bg-primary/5 border border-primary/10 italic text-primary/80">
{t('about.intro.belief')}
</div>
</div>
</HighlightBox>
<!-- Self Intro -->
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Sparkles className="w-6 h-6 text-primary" />
{t('about.me.title')}
</h2>
<div class="prose dark:prose-invert max-w-none">
<p class="text-lg leading-relaxed text-muted-foreground whitespace-pre-line">
{t('about.me.content')}
</p>
</div>
</div>
<!-- Skills Grid -->
<div class="grid md:grid-cols-2 gap-8">
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Zap className="w-6 h-6 text-yellow-500" />
{t('about.skills.mastered.title')}
</h2>
<div class="flex flex-wrap gap-2">
{(t('about.skills.mastered.items') as unknown as string[]).map((item: string) => (
<div class="px-3 py-1.5 rounded-lg bg-primary/5 border border-primary/10 text-muted-foreground text-xs font-medium">
{item}
</div>
))}
</div>
</div>
<div class="page-surface p-8 rounded-2xl border bg-card/40 backdrop-blur-md space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Heart className="w-6 h-6 text-red-500" />
{t('about.skills.learning.title')}
</h2>
<div class="flex flex-wrap gap-2">
{(t('about.skills.learning.items') as unknown as string[]).map((item: string) => (
<div class="px-3 py-1.5 rounded-lg bg-muted/30 border border-border/50 text-muted-foreground/70 text-xs font-medium">
{item}
</div>
))}
</div>
</div>
</div>
<!-- Interests -->
<div class="space-y-8">
<h2 class="text-2xl font-bold flex items-center gap-3 px-2">
<Heart className="w-6 h-6 text-red-500" />
{t('about.interests.title')}
</h2>
<ul class="grid sm:grid-cols-2 gap-4">
{(t('about.interests.items') as unknown as any[]).map((item) => (
<li class="flex items-start gap-4 p-4 rounded-xl border border-border/40 bg-card/20 hover:bg-card/40 transition-colors group">
<div class="mt-1.5 w-1.5 h-1.5 rounded-full bg-primary/40 group-hover:bg-primary transition-colors shrink-0" />
<div class="space-y-1">
<span class="font-bold text-foreground">{item.title}</span>
<p class="text-sm text-muted-foreground leading-relaxed">{item.content}</p>
</div>
</li>
))}
</ul>
</div>
<!-- Socials & Community -->
<div class="space-y-6">
<h2 class="text-2xl font-bold flex items-center gap-3 px-2">
<Globe className="w-6 h-6 text-primary" />
{t('about.socials.title')}
</h2>
<div class="flex flex-wrap gap-4 px-2">
{(t('about.socials.items') as unknown as any[]).map((item) => {
const Icon = IconMap[item.icon] || Globe;
return (
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-2 px-4 py-2 rounded-lg bg-muted/30 border border-border/50 hover:border-primary/50 hover:bg-primary/5 transition-all group text-sm font-medium"
>
<Icon className="w-4 h-4 text-muted-foreground group-hover:text-primary transition-colors" />
<span>{item.label}</span>
</a>
);
})}
</div>
</div>
<!-- Contact -->
<div class="space-y-8 py-8">
<div class="px-2 space-y-4">
<h2 class="text-2xl font-bold flex items-center gap-3">
<Mail className="w-6 h-6 text-primary" />
{t('about.contact.title')}
</h2>
<p class="text-muted-foreground leading-relaxed max-w-2xl">
{t('about.contact.warning')}
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{(t('about.contact.methods') as unknown as any[]).map((method) => {
const Icon = IconMap[method.icon] || MessageSquare;
const content = (
<div class="flex items-center gap-4 p-4 rounded-xl border border-border/40 bg-card/20 hover:bg-card/40 transition-all group">
<div class="p-2.5 rounded-lg bg-primary/5 text-primary/70 group-hover:bg-primary/10 group-hover:text-primary transition-colors">
<Icon className="w-5 h-5" />
</div>
<div class="flex flex-col">
<span class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest">{method.label}</span>
<span class="text-sm font-medium break-all">{method.value}</span>
</div>
</div>
);
if (method.link) {
return (
<a href={method.link} class="block transition-transform hover:-translate-y-0.5">
{content}
</a>
);
}
return <div class="cursor-default">{content}</div>;
})}
</div>
</div>
</div>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main" id="overview">
<div class="mb-6 flex flex-wrap gap-2 text-sm">
<a href="#overview" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '概览' : 'Overview'}</a>
<a href="#story" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '故事' : 'Story'}</a>
<a href="#capabilities" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '能力' : 'Capabilities'}</a>
<a href="#principles" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '原则' : 'Principles'}</a>
<a href="#timeline" class="rounded-md border border-border px-3 py-1 hover:bg-muted">{isZh ? '时间线' : 'Timeline'}</a>
<a href="#uses" class="rounded-md border border-border px-3 py-1 hover:bg-muted">Uses</a>
</div>
</Container>
</section>
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '关于我' : 'About'}</h1>
<p class="mt-6 text-lg leading-relaxed text-muted-foreground">
{isZh
? '我是一名长期在复杂业务场景中工作的 AI 全栈工程师。这个页面主要展示我的经历、能力与做事方式。'
: 'I am an AI full-stack engineer working in complex product environments. This page focuses on my experience, capability, and way of working.'}
</p>
</section>
<section class="page-content-main mt-10 page-surface p-8" id="story">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '个人叙事' : 'Narrative'}</h2>
<div class="mt-4 space-y-4 text-muted-foreground">
{aboutNarrative.map((line) => <p>{line[lang]}</p>)}
</div>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="capabilities">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '核心能力' : 'Core Capabilities'}</h2>
<ul class="mt-4 grid gap-3 md:grid-cols-2 text-muted-foreground">
{aboutCapabilities.map((item) => (
<li class="flex gap-3 rounded-md border border-border/70 p-3"><span class="mt-2 h-1.5 w-1.5 rounded-full bg-primary"></span><span>{item[lang]}</span></li>
))}
</ul>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="principles">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '工作原则' : 'Working Principles'}</h2>
<ul class="mt-4 space-y-3 text-muted-foreground">
{aboutPrinciples.map((item) => (
<li class="flex gap-3"><span class="mt-2 h-1.5 w-1.5 rounded-full bg-primary"></span><span>{item[lang]}</span></li>
))}
</ul>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="timeline">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '经历时间线' : 'Career Timeline'}</h2>
<div class="mt-5 space-y-4">
{brandTimeline.map((item) => (
<article class="rounded-md border border-border/70 p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{item.period[lang]}</p>
<h3 class="mt-1 text-base font-bold">{item.title[lang]}</h3>
<p class="mt-2 text-sm text-muted-foreground">{item.summary[lang]}</p>
{item.highlights && item.highlights.length > 0 && (
<ul class="mt-3 space-y-1.5 text-sm text-foreground/90">
{item.highlights.map((line) => <li>• {line[lang]}</li>)}
</ul>
)}
</article>
))}
</div>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="uses">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '工具与工作流' : 'Uses'}</h2>
<div class="mt-5 grid gap-5 md:grid-cols-2">
{uses.map((group) => (
<article class="rounded-md border border-border/70 p-5">
<h3 class="text-base font-bold">{group.title[lang]}</h3>
<ul class="mt-3 space-y-1.5 text-sm text-muted-foreground">
{group.items.map((item) => <li>• {item}</li>)}
</ul>
</article>
))}
</div>
</section>
<section class="page-content-main mt-6 page-surface p-8" id="next-step">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '下一步' : 'Next Step'}</h2>
<p class="mt-3 text-muted-foreground">
{isZh
? '如果你想进一步判断合作匹配度,可以继续查看案例页与合作页。'
: 'If you want to assess collaboration fit, continue to the project cases and collaboration page.'}
</p>
<div class="mt-6 flex flex-wrap gap-3">
<a href={`${prefix}/projects`} class="inline-flex h-10 items-center rounded-lg bg-primary px-5 text-sm font-semibold text-primary-foreground">{isZh ? '查看案例' : 'View Projects'}</a>
<a href={`${prefix}/hire`} class="inline-flex h-10 items-center rounded-lg border border-border px-5 text-sm font-semibold hover:bg-muted">{isZh ? '查看合作页' : 'View Hire Page'}</a>
</div>
<div class="mt-5 border-t border-border/70 pt-4 text-sm">
<p class="font-semibold text-foreground">GitHub</p>
<a href={personalInfo.github} target="_blank" rel="noopener noreferrer" class="text-primary hover:text-primary/80 break-all">{personalInfo.github}</a>
</div>
</section>
<ContactCard lang={lang} />
</Container>
</main>
<Footer client:load lang={lang} />
<Footer lang={lang} client:load />
</Layout>

View File

@@ -81,7 +81,7 @@ if (categoryName === decodedCategory) {
}
// Generate page title and description
const title = `${categoryName} - 博客 | Joey Zhao`;
const title = `${categoryName} - 博客 | Joey Z.`;
const description = `探索关于${categoryName}的文章。深入了解我对${categoryName}及相关主题的思考。`;
---

View File

@@ -3,32 +3,25 @@ import BlogLayout from '../../../layouts/BlogLayout.astro';
import BlogList from '../../../components/blog/BlogList.astro';
import CategoryCard from '../../../components/blog/CategoryCard.astro';
import TagCard from '../../../components/blog/TagCard.astro';
import Container from "../../../components/ui/Container.astro";
import Container from '../../../components/ui/Container.astro';
import { type BlogPost } from '@/types';
import { useTranslations } from '@/i18n/utils';
import { type Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
import { sortPostsByDate, extractCategories, extractTags } from '@/utils/blog-utils';
import { sortPostsByDate } from '@/utils/blog-utils';
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
// 使用import.meta.glob读取所有中文博客文章
const allPosts = await import.meta.glob('./posts/*.md', { eager: true });
// 处理博客文章数据
const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => {
const slug = post.url?.split('/').filter(Boolean).pop() || '';
// 获取文章的默认图片如果frontmatter中没有指定
const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center";
const defaultImage = 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center';
return {
title: post.frontmatter.title,
description: post.frontmatter.description || '',
image: post.frontmatter.image || defaultImage,
slug: slug,
slug,
tags: post.frontmatter.tags || [],
tagId: post.frontmatter.tagId || [],
category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [],
@@ -38,51 +31,29 @@ const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => {
};
});
// 使用工具函数按日期排序
const sortedBlogPosts = sortPostsByDate(blogPosts);
// 提取所有文章的分类和标签信息(用于侧边栏)
const allPostsArray = Object.values(allPosts).map((post: any) => ({
category: post.frontmatter.category || [],
categoryId: post.frontmatter.categoryId || [],
tags: post.frontmatter.tags || [],
tagId: post.frontmatter.tagId || []
}));
// 使用工具函数提取分类和标签
const categories = extractCategories(allPostsArray);
const tags = extractTags(allPostsArray);
---
<BlogLayout title="博客 - Joey Zhao" description="关于 AI 产品、全栈开发和公开构建的思考。探索技术与产品构建的交汇点。">
<BlogLayout title="写作 - Joey Z." description="聚焦架构设计、工程交付与 AI 协作开发实践的技术写作。">
<main class="min-h-screen">
<!-- 头部区域 -->
<Container className="pt-24 pb-12">
<div class="text-center mb-16">
<h1 class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-foreground via-primary to-primary dark:from-foreground dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent mb-6">
<span class="text-primary">博客</span>
<span class="text-primary">写作</span>
</h1>
<p class="text-xl text-muted-foreground page-content-main">
关于 AI 产品、全栈开发和公开构建的思考。探索技术与产品构建的交汇点
聚焦架构设计、工程交付与 AI 协作开发实践的技术写作
</p>
</div>
</Container>
<!-- 主要内容 -->
<Container className="pb-20">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
<!-- 侧边栏 -->
<div class="lg:col-span-1 space-y-8">
<!-- 分类卡片 -->
<CategoryCard lang="zh" />
<!-- 标签卡片 -->
<TagCard lang="zh" />
</div>
<!-- 博客文章 -->
<div class="lg:col-span-3">
<BlogList posts={sortedBlogPosts} lang="zh" />
</div>

View File

@@ -74,7 +74,7 @@ if (!tagName) {
}
// 动态生成页面标题和描述
const pageTitle = `# ${tagName} - 博客 | Joey Zhao`;
const pageTitle = `# ${tagName} - 博客 | Joey Z.`;
const pageDescription = `浏览带有 # ${tagName} 标签的文章。深入了解我关于 ${tagName} 及相关主题的想法。`;
---

View File

@@ -0,0 +1,3 @@
---
return Astro.redirect('/zh/about#contact-card', 301);
---

View File

@@ -1,333 +1,105 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import { useTranslations } from "@/i18n/utils";
import { Users, Wrench, Lightbulb, Handshake, CircleDollarSign, Clock, Target, Check, Calendar, Mail, Radio, Zap, Globe, Smartphone, ShoppingCart, Layout as LayoutIcon, ClipboardList } from "lucide-react";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import ContactCard from '@/components/ContactCard.astro';
import {
collaborationModels,
collaborationFitSignals,
collaborationProcess,
collaborationFaq,
} from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
const lang = (Astro.currentLocale as Lang) || defaultLang;
const isZh = lang === 'zh';
---
<Layout title={pageTitle}>
<Layout title={isZh ? '合作' : 'Hire'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<main class="min-h-screen">
<!-- Hire Page Hero -->
<section class="py-24 relative overflow-hidden">
<div class="page-hero-overlay"></div>
<Container className="relative z-10">
<div class="text-center mb-16">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4">
专业合作
</span>
<h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
合作
</h1>
<p class="text-lg text-muted-foreground page-content-narrow">
构建优秀产品,从找到合适的技术合伙人开始
</p>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main">
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '合作方式' : 'Work With Me'}</h1>
<p class="mt-4 text-lg leading-relaxed text-muted-foreground">
{isZh
? '这个页面只做一件事:帮助你快速判断我们是否匹配。'
: 'This page does one thing: help you quickly evaluate whether we are a good fit.'}
</p>
</section>
<section class="page-content-main mt-10">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '合作模型Mock' : 'Collaboration Models (Mock)'}</h2>
<div class="mt-4 grid gap-4 md:grid-cols-3">
{collaborationModels.map((model) => (
<article class="page-surface p-5">
<h3 class="text-lg font-bold">{model.title[lang]}</h3>
<p class="mt-2 text-sm text-muted-foreground">{model.description[lang]}</p>
</article>
))}
</div>
</Container>
</section>
</section>
<!-- Who I Work With -->
<section class="py-16 relative">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-primary/20 rounded-xl flex items-center justify-center">
<Users className="w-6 h-6 text-primary" />
</div>
<h2 class="text-3xl font-bold">
我与谁合作
</h2>
</div>
<section class="page-content-main mt-6 grid gap-6 md:grid-cols-2">
<article class="page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '匹配信号' : 'Good Fit Signals'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
{collaborationFitSignals.map((item) => <li>• {item[lang]}</li>)}
</ul>
</article>
<div class="grid md:grid-cols-2 gap-6">
<div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3">
初创公司
</h3>
<p class="text-muted-foreground">
从零到一构建产品的早期创业团队,需要快速迭代和灵活响应的技术方案。
</p>
</div>
<article class="page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '合作流程' : 'Collaboration Process'}</h2>
<ol class="mt-4 space-y-2 text-sm text-muted-foreground list-decimal pl-5">
{collaborationProcess.map((item) => <li>{item[lang]}</li>)}
</ol>
</article>
</section>
<div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3">
独立创始人
</h3>
<p class="text-muted-foreground">
独自构建产品的创业者,需要技术合伙人来实现产品愿景。
</p>
</div>
<section class="page-content-main mt-6 page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '默认协作约定' : 'Default Working Agreement'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
<li>• {isZh ? '以清晰目标和边界为前提,不做无定义范围的推进。' : 'We start with clear outcomes and boundaries, not ambiguous execution.'}</li>
<li>• {isZh ? '同步节奏可按周设置,关键节点保持可见状态更新。' : 'Cadence is usually weekly, with visible updates at key checkpoints.'}</li>
<li>• {isZh ? '文档和决策记录优先,降低沟通成本与返工风险。' : 'Documentation and decision logs are prioritized to reduce rework.'}</li>
</ul>
</section>
<div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3">
传统企业
</h3>
<p class="text-muted-foreground">
希望数字化转型的传统企业,需要现代化的技术解决方案。
</p>
<section class="page-content-main mt-6 page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '常见问题Mock' : 'FAQ (Mock)'}</h2>
<div class="mt-4 space-y-4">
{collaborationFaq.map((item) => (
<div class="rounded-md border border-border/70 p-4">
<p class="text-sm font-semibold">{item.question[lang]}</p>
<p class="mt-2 text-sm text-muted-foreground">{item.answer[lang]}</p>
</div>
<div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3">
技术团队
</h3>
<p class="text-muted-foreground">
需要额外技术资源或特定技能集来推进项目的开发团队。
</p>
</div>
</div>
))}
</div>
</Container>
</section>
</section>
<!-- What I Build -->
<section class="py-16 relative page-section-soft">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center">
<Wrench className="w-6 h-6 text-blue-500" />
</div>
<h2 class="text-3xl font-bold">
我能构建什么
</h2>
</div>
<div class="space-y-6">
<div class="page-surface p-8">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0">
<Zap className="w-6 h-6 text-white" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">AI 产品与应用</h3>
<p class="text-muted-foreground">
从 AI 助手到 Agent 系统,从 RAG 应用到 AI 驱动的 SaaS 产品。我可以帮助你将 AI 能力产品化。
</p>
</div>
</div>
</div>
<div class="page-surface p-8">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-green-600 to-teal-600 rounded-xl flex items-center justify-center flex-shrink-0">
<Globe className="w-6 h-6 text-white" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">Web 应用</h3>
<p class="text-muted-foreground">
现代 Web 应用、SaaS 平台、电商系统、内容管理系统等。使用 React、Next.js、Node.js 等技术栈。
</p>
</div>
</div>
</div>
<div class="page-surface p-8">
<div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-orange-600 to-red-600 rounded-xl flex items-center justify-center flex-shrink-0">
<Smartphone className="w-6 h-6 text-white" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">全栈产品</h3>
<p class="text-muted-foreground">
从前端到后端,从数据库到部署,我提供完整的全栈开发能力,一个项目只需一个开发者。
</p>
</div>
</div>
</div>
</div>
<section class="page-content-main mt-6 page-surface p-6">
<h2 class="text-xl font-bold">{isZh ? '下一步' : 'Next Step'}</h2>
<p class="mt-3 text-sm text-muted-foreground">
{isZh
? '如果你觉得方向匹配,直接在本页下方联系卡片里选择你习惯的方式联系我。'
: 'If this direction fits, use the contact card below and send a short context brief.'}
</p>
<div class="mt-5 flex flex-wrap gap-3">
<a href="#contact-card" class="inline-flex h-10 items-center rounded-lg bg-primary px-5 text-sm font-semibold text-primary-foreground">
{isZh ? '查看联系卡片' : 'Open Contact Card'}
</a>
<a href={isZh ? '/zh/projects' : '/projects'} class="inline-flex h-10 items-center rounded-lg border border-border px-5 text-sm font-semibold hover:bg-muted">
{isZh ? '先看案例' : 'View Cases First'}
</a>
</div>
</Container>
</section>
</section>
<!-- Engagement Models -->
<section class="py-16 relative">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center">
<ClipboardList className="w-6 h-6 text-green-500" />
</div>
<h2 class="text-3xl font-bold">
合作模式
</h2>
</div>
<div class="grid md:grid-cols-3 gap-6">
<div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<Handshake className="w-6 h-6 text-green-600 dark:text-green-400" />
</div>
<h3 class="text-lg font-bold mb-2">
技术合伙人
</h3>
<p class="text-sm text-muted-foreground">
长期合作,股权合作,共同承担创业风险与回报
</p>
</div>
<div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<CircleDollarSign className="w-6 h-6 text-blue-600 dark:text-blue-400" />
</div>
<h3 class="text-lg font-bold mb-2">
项目制
</h3>
<p class="text-sm text-muted-foreground">
按项目交付,固定价格或按阶段付款,适合明确需求的项目
</p>
</div>
<div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-primary/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<Clock className="w-6 h-6 text-primary" />
</div>
<h3 class="text-lg font-bold mb-2">
咨询/顾问
</h3>
<p class="text-sm text-muted-foreground">
按小时咨询,帮助你做出技术决策,审查代码,指导团队
</p>
</div>
</div>
</div>
</Container>
</section>
<!-- Delivery Style -->
<section class="py-16 relative page-section-soft">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-yellow-500/20 rounded-xl flex items-center justify-center">
<Target className="w-6 h-6 text-yellow-600 dark:text-yellow-400" />
</div>
<h2 class="text-3xl font-bold">
工作方式
</h2>
</div>
<div class="page-surface p-8">
<ul class="space-y-4">
<li class="flex items-start gap-3">
<Check className="w-6 h-6 text-green-500 shrink-0 mt-0.5" />
<div>
<strong class="block mb-1">敏捷迭代</strong>
<p class="text-muted-foreground text-sm">
2周一个迭代快速交付可用功能持续获取反馈
</p>
</div>
</li>
<li class="flex items-start gap-3">
<Check className="w-6 h-6 text-green-500 shrink-0 mt-0.5" />
<div>
<strong class="block mb-1">透明沟通</strong>
<p class="text-muted-foreground text-sm">
定期同步进度,及时报告问题,保持信息对称
</p>
</div>
</li>
<li class="flex items-start gap-3">
<Check className="w-6 h-6 text-green-500 shrink-0 mt-0.5" />
<div>
<strong class="block mb-1">代码质量</strong>
<p class="text-muted-foreground text-sm">
完整的测试覆盖,清晰的文档注释,可维护的代码结构
</p>
</div>
</li>
<li class="flex items-start gap-3">
<Check className="w-6 h-6 text-green-500 shrink-0 mt-0.5" />
<div>
<strong class="block mb-1">产品思维</strong>
<p class="text-muted-foreground text-sm">
不只是写代码,还思考产品价值,用户体验和业务目标
</p>
</div>
</li>
</ul>
</div>
</div>
</Container>
</section>
<!-- Availability -->
<section class="py-16 relative">
<Container>
<div class="page-content-main">
<div class="flex items-center gap-4 mb-8">
<div class="w-12 h-12 bg-red-500/20 rounded-xl flex items-center justify-center">
<Calendar className="w-6 h-6 text-red-500" />
</div>
<h2 class="text-3xl font-bold">
当前可用性
</h2>
</div>
<div class="page-surface p-8">
<div class="flex items-center gap-4 mb-6">
<div class="w-4 h-4 bg-green-500 rounded-full animate-pulse"></div>
<span class="text-lg font-semibold">
目前可接受新项目
</span>
</div>
<p class="text-muted-foreground mb-6">
我目前可以接受 1-2 个新项目。偏好长期合作模式(技术合伙人或长期项目),但也会考虑短期项目。
</p>
<div class="flex flex-wrap gap-2">
<span class="px-3 py-1 bg-green-500/20 text-green-600 dark:text-green-400 rounded-full text-sm">
可立即开始
</span>
<span class="px-3 py-1 bg-blue-500/20 text-blue-600 dark:text-blue-400 rounded-full text-sm">
远程工作
</span>
<span class="px-3 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm">
中英双语
</span>
</div>
</div>
</div>
</Container>
</section>
<!-- Contact CTA -->
<section class="py-16 relative page-section-soft">
<Container>
<div class="page-content-main text-center">
<h2 class="text-3xl font-bold mb-6">
准备好一起构建了吗?
</h2>
<p class="text-lg text-muted-foreground mb-8 page-content-narrow">
告诉我你的项目想法,让我们看看能否合作。
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="mailto:zhaoguiyang18@gmail.com"
class="bg-primary hover:bg-primary/90 text-white px-8 py-3 rounded-lg font-semibold transition-colors inline-flex items-center justify-center gap-2"
>
<Mail className="w-5 h-5" />
发送邮件
</a>
<a
href="/zh/now"
class="border border-primary text-primary hover:bg-primary hover:text-white px-8 py-3 rounded-lg font-semibold transition-colors inline-flex items-center justify-center gap-2"
>
<Radio className="w-5 h-5" />
了解更多关于我
</a>
</div>
</div>
</Container>
</section>
<ContactCard lang={lang} showIntents={false} />
</Container>
</main>
<Footer lang={lang} client:only="react" />
<Footer lang={lang} client:load />
</Layout>

View File

@@ -1,282 +1,184 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import { useTranslations } from "@/i18n/utils";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import { personalInfo, services, projects } from "@/lib/data/index";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import {
personalInfo,
projects,
identityFacts,
heroPrinciples,
heroSkillGroups,
brandTimeline,
} from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
import { sortPostsByDate } from '@/utils/blog-utils';
const lang = (Astro.currentLocale as Lang) || defaultLang;
const t = useTranslations(lang);
const pageTitle = t("site.title");
const prefix = lang === "zh" ? "/zh" : "";
const isZh = lang === 'zh';
const prefix = isZh ? '/zh' : '';
const featuredProjects = projects[lang].filter((item) => item.featured).slice(0, 3);
const terminalFacts = identityFacts.filter((item) => item.label.en !== 'Experience');
const localizedServices = services[lang];
const featuredProjects = projects[lang].filter((project) => project.featured);
const careerMilestones = [
{
period: t("home.career.card1.period"),
title: t("home.career.card1.title"),
outcome: t("home.career.card1.outcome"),
},
{
period: t("home.career.card2.period"),
title: t("home.career.card2.title"),
outcome: t("home.career.card2.outcome"),
},
{
period: t("home.career.card3.period"),
title: t("home.career.card3.title"),
outcome: t("home.career.card3.outcome"),
},
];
const keyFacts = [
t("home.trust.item1"),
t("home.trust.item3"),
t("home.trust.item4"),
];
const postModules = await import.meta.glob('./blog/posts/*.md', { eager: true });
const latestPosts = sortPostsByDate(
Object.values(postModules).map((post: any) => ({
title: post.frontmatter.title,
description: post.frontmatter.description || '',
slug: post.url?.split('/').filter(Boolean).pop() || '',
date: post.frontmatter.date || post.frontmatter.pubDate || '',
})),
).slice(0, 3);
---
<Layout title={pageTitle}>
<GlassHeader client:load transition:persist="header" lang={lang} />
<Layout title={isZh ? '首页' : 'Home'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<main class="min-h-screen">
<div class="relative overflow-hidden">
<div class="absolute inset-0 -z-10">
<div class="absolute left-1/2 top-0 h-[600px] w-[800px] -translate-x-1/2 -translate-y-1/2 bg-primary/10 blur-[120px]"></div>
<div class="absolute right-0 top-1/4 h-[400px] w-[400px] bg-purple-500/5 blur-[100px]"></div>
<div class="absolute left-0 top-1/2 h-[400px] w-[400px] bg-orange-500/5 blur-[100px]"></div>
</div>
<section class="relative flex min-h-[90vh] items-center py-24 lg:py-32">
<Container className="relative z-10">
<div class="mx-auto max-w-5xl space-y-16">
<div class="space-y-8 text-center">
<div class="space-y-4">
<h1 class="text-5xl font-bold tracking-tight sm:text-7xl lg:text-8xl">
<span class="bg-gradient-to-b from-foreground to-foreground/70 bg-clip-text text-transparent">{personalInfo.name}</span>
</h1>
<p class="text-2xl font-medium text-foreground/80 sm:text-3xl">{personalInfo.position[lang]}</p>
</div>
<p class="mx-auto max-w-2xl text-lg leading-relaxed text-muted-foreground/90 sm:text-xl">{t("home.hero.summary")}</p>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main space-y-6">
<p class="text-sm font-semibold uppercase tracking-[0.2em] text-primary">{isZh ? 'TERMINAL PROFILE' : 'TERMINAL PROFILE'}</p>
<div class="flex flex-wrap items-center justify-center gap-4">
<a href={`${prefix}/about`} class="inline-flex h-12 items-center justify-center rounded-full bg-primary px-8 text-sm font-semibold text-primary-foreground shadow-lg shadow-primary/20 transition-all hover:translate-y-[-2px] hover:bg-primary/90 hover:shadow-xl hover:shadow-primary/30">
{t("home.hero.ctaPrimary")}
</a>
<a href={`${prefix}/hire`} class="inline-flex h-12 items-center justify-center rounded-full border border-border bg-background/50 px-8 text-sm font-semibold text-foreground backdrop-blur-sm transition-all hover:translate-y-[-2px] hover:bg-muted">
{t("home.hero.ctaSecondary")}
</a>
</div>
<div class="flex flex-wrap items-center justify-center gap-3 text-sm">
{keyFacts.map((item) => (
<span class="inline-flex items-center rounded-full border border-primary/10 bg-primary/5 px-4 py-1.5 font-medium text-primary/80 backdrop-blur-sm">
<span class="mr-2 h-1 w-1 rounded-full bg-primary/50"></span>
{item}
</span>
))}
</div>
</div>
<div class="mx-auto max-w-3xl">
<div class="group relative">
<div class="absolute -inset-1 rounded-2xl bg-gradient-to-r from-primary/20 to-purple-500/20 opacity-50 blur transition duration-1000 group-hover:opacity-100 group-hover:duration-200"></div>
<div class="page-surface relative overflow-hidden rounded-2xl border bg-card/40 backdrop-blur-md">
<div class="flex items-center justify-between border-b border-border/50 bg-muted/20 px-5 py-3">
<div class="flex items-center gap-2">
<span class="h-3 w-3 rounded-full bg-red-400/80 shadow-[0_0_8px_rgba(248,113,113,0.4)]"></span>
<span class="h-3 w-3 rounded-full bg-yellow-400/80 shadow-[0_0_8px_rgba(250,204,21,0.4)]"></span>
<span class="h-3 w-3 rounded-full bg-green-400/80 shadow-[0_0_8px_rgba(74,222,128,0.4)]"></span>
</div>
<span class="font-mono text-xs font-medium text-muted-foreground/70 tracking-tight">{personalInfo.terminal.username}</span>
</div>
<div class="space-y-3 p-6 font-mono text-sm leading-relaxed text-foreground/90">
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">whoami</span> {personalInfo.name}</p>
</div>
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">role</span> {personalInfo.position[lang]}</p>
</div>
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">stack</span> TypeScript, React, Node.js, Astro</p>
</div>
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">status</span> {t("home.hero.terminalStatus")}</p>
</div>
<div class="flex gap-3">
<span class="text-primary font-bold">~</span>
<p><span class="text-primary/70">contact</span> {personalInfo.github}</p>
</div>
</div>
</div>
</div>
</div>
<div class="space-y-3">
<h1 class="text-4xl font-bold tracking-tight text-foreground sm:text-5xl lg:text-6xl">{personalInfo.name}</h1>
<p class="text-lg font-medium text-primary/90">{personalInfo.position[lang]}</p>
</div>
</Container>
</section>
<div class="space-y-40 pb-40">
<section class="relative">
<div class="py-24 lg:py-32">
<Container>
<div class="mb-16 flex flex-col gap-6 md:flex-row md:items-end md:justify-between">
<div class="space-y-4">
<h2 class="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">{t("home.services.title")}</h2>
<p class="max-w-2xl text-lg leading-relaxed text-muted-foreground/90">{t("home.services.description")}</p>
</div>
<a href={`${prefix}/services`} class="inline-flex items-center text-sm font-bold text-primary transition-colors hover:text-primary/70">
{t("services.viewAll")}
<svg class="ml-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"/></svg>
</a>
<div class="space-y-3 text-xs leading-relaxed sm:text-sm">
{heroPrinciples.map((line, index) => (
<p
class={
index === 0
? 'inline-flex items-center rounded-md border border-primary/35 bg-primary/10 px-3 py-1.5 font-semibold text-primary'
: 'text-foreground/80'
}
>
{index === 0 && <span class="mr-2 text-[10px] uppercase tracking-wider text-primary/80">{isZh ? 'Now Hiring' : 'Open to Work'}</span>}
{line[lang]}
</p>
))}
</div>
<article class="page-surface overflow-hidden border border-border/80">
<div class="flex items-center justify-between border-b border-border/80 px-4 py-2">
<div class="flex items-center gap-2">
<span class="h-2.5 w-2.5 rounded-full bg-red-400"></span>
<span class="h-2.5 w-2.5 rounded-full bg-yellow-400"></span>
<span class="h-2.5 w-2.5 rounded-full bg-green-400"></span>
</div>
<p class="text-xs font-mono text-muted-foreground">joey@engineer:~</p>
</div>
<div class="space-y-6 p-5 font-mono text-sm">
<div class="space-y-2">
<p><span class="text-primary">$</span> whoami</p>
<p class="pl-4 text-foreground">{personalInfo.name}</p>
</div>
<div class="grid gap-10 md:grid-cols-2">
{localizedServices.map((service) => (
<article class="group relative rounded-3xl p-6 transition-all duration-500 hover:bg-card/50 hover:shadow-xl hover:shadow-primary/5">
<div class="mb-8 flex items-center gap-6">
<div class={`flex h-14 w-14 items-center justify-center rounded-2xl bg-gradient-to-br ${service.icon.gradient} text-white shadow-lg transition-transform duration-500 group-hover:scale-110 group-hover:rotate-3`}>
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<Fragment set:html={service.icon.svg} />
</svg>
<div class="space-y-2">
<p><span class="text-primary">$</span> profile --summary</p>
<div class="grid gap-2 pl-4 text-muted-foreground sm:grid-cols-2">
{terminalFacts.map((fact) => (
<p><span class="text-foreground/90">{fact.label[lang]}:</span> {fact.value[lang]}</p>
))}
</div>
</div>
<div class="space-y-3">
<p><span class="text-primary">$</span> stack --list</p>
<div class="space-y-3 pl-4">
{heroSkillGroups.map((group) => (
<div>
<p class="text-xs uppercase tracking-wide text-primary/80">{group.name[lang]}</p>
<div class="mt-2 flex flex-wrap gap-2">
{group.items.map((item) => (
<span class="rounded-md border border-border/80 bg-muted/40 px-2 py-1 text-xs text-foreground/90">{item}</span>
))}
</div>
<h3 class="text-2xl font-bold tracking-tight">{service.title}</h3>
</div>
<ul class="space-y-4">
{service.items.slice(0, 3).map((item) => (
<li class="flex items-start gap-3 text-muted-foreground group-hover:text-foreground/90 transition-colors">
<span class="mt-2.5 h-1 w-1 shrink-0 rounded-full bg-primary/30 group-hover:bg-primary transition-all"></span>
<span class="text-base leading-relaxed">{item}</span>
</li>
))}
))}
</div>
</div>
</div>
</article>
</section>
<section class="page-content-main mt-20">
<div class="mb-8 flex items-end justify-between gap-4">
<h2 class="text-2xl font-bold tracking-tight sm:text-3xl">{isZh ? '经历速览' : 'Career Snapshot'}</h2>
<a href={`${prefix}/about#timeline`} class="text-sm font-semibold text-primary hover:text-primary/80">{isZh ? '查看完整经历' : 'View Full Timeline'}</a>
</div>
<div class="relative pl-6">
<div class="absolute left-[0.7rem] top-2 h-[calc(100%-0.5rem)] w-px bg-border"></div>
<div class="space-y-6">
{brandTimeline.map((item) => (
<article class="relative rounded-lg border border-border/70 bg-card/60 p-5">
<span class="absolute -left-[1.06rem] top-6 h-3 w-3 rounded-full border-2 border-primary bg-background"></span>
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{item.period[lang]}</p>
<h3 class="mt-2 text-base font-bold leading-snug">{item.title[lang]}</h3>
<p class="mt-2 text-sm text-muted-foreground">{item.summary[lang]}</p>
{item.highlights && item.highlights.length > 0 && (
<ul class="mt-3 space-y-1.5 text-sm text-foreground/90">
{item.highlights.map((line) => <li>• {line[lang]}</li>)}
</ul>
</article>
))}
</div>
</Container>
)}
</article>
))}
</div>
</div>
</section>
<section class="relative">
<Container>
<div class="mb-16 space-y-3">
<h2 class="text-4xl font-bold tracking-tight sm:text-5xl">{t("home.career.title")}</h2>
<p class="max-w-xl text-lg text-muted-foreground">{t("home.career.description")}</p>
</div>
<section class="page-content-main mt-20">
<div class="mb-8 flex items-end justify-between gap-4">
<h2 class="text-2xl font-bold tracking-tight sm:text-3xl">{isZh ? '项目经历' : 'Project Experience'}</h2>
<a href={`${prefix}/projects`} class="text-sm font-semibold text-primary hover:text-primary/80">{isZh ? '查看全部项目' : 'View All Projects'}</a>
</div>
<div class="relative mx-auto max-w-4xl">
<div class="absolute left-0 top-0 h-full w-px bg-gradient-to-b from-primary/50 via-primary/10 to-transparent sm:left-1/2"></div>
<div class="space-y-12">
{careerMilestones.map((item, index) => (
<div class={`relative flex flex-col sm:flex-row ${index % 2 === 0 ? 'sm:flex-row-reverse' : ''}`}>
<div class="absolute -left-1.5 top-2 h-3 w-3 rounded-full border-2 border-primary bg-background sm:left-1/2 sm:-ml-1.5"></div>
<div class="w-full pl-8 sm:w-1/2 sm:px-12">
<article class="rounded-2xl p-4 transition-all hover:bg-card/40">
<span class="text-xs font-bold uppercase tracking-wider text-primary/60">{item.period}</span>
<h3 class="mt-2 text-xl font-bold leading-tight">{item.title}</h3>
<p class="mt-3 text-sm leading-relaxed text-muted-foreground/90">{item.outcome}</p>
</article>
</div>
</div>
))}
</div>
</div>
<div class="mt-16 text-center">
<a href={`${prefix}/about`} class="inline-flex h-12 items-center rounded-full border border-border bg-background px-8 text-sm font-semibold transition-all hover:bg-muted">
{t("home.career.cta")}
</a>
</div>
</Container>
</section>
<section class="relative">
<div class="py-24 lg:py-32">
<Container>
<div class="mb-12 space-y-3">
<h2 class="text-4xl font-bold tracking-tight sm:text-5xl">{t("home.featured.title")}</h2>
</div>
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{featuredProjects.map((project) => (
<article class="group flex flex-col overflow-hidden rounded-3xl border border-border/50 bg-card/50 backdrop-blur-sm transition-all duration-500 hover:-translate-y-2 hover:shadow-2xl hover:shadow-primary/5">
{/* 顶部封面图区域 */}
<div class="relative aspect-video overflow-hidden">
<div class={`absolute inset-0 bg-gradient-to-br ${project.image?.bg || 'from-primary/20 to-purple-500/20'} transition-transform duration-700 group-hover:scale-110`}></div>
<div class="absolute inset-0 flex items-center justify-center">
<span class="text-6xl transition-transform duration-500 group-hover:scale-110 group-hover:rotate-3">{project.icon}</span>
</div>
<div class="absolute top-4 right-4">
<span class="rounded-full bg-background/80 px-3 py-1 text-[10px] font-bold uppercase tracking-wider text-foreground backdrop-blur-md">
{project.type}
</span>
</div>
</div>
{/* 内容区域 */}
<div class="flex flex-1 flex-col p-6 lg:p-8">
<div class="flex-1 space-y-4">
<h3 class="text-2xl font-bold tracking-tight">{project.title}</h3>
<p class="line-clamp-2 text-sm leading-relaxed text-muted-foreground">
{project.impact}
</p>
<div class="flex flex-wrap gap-2">
{project.tech.map((tech) => (
<span class="rounded-md bg-primary/5 px-2 py-1 text-[10px] font-medium text-primary/80">
{tech}
</span>
))}
</div>
</div>
{/* 操作按钮 */}
<div class="mt-8 flex items-center justify-between gap-4">
<a href={`${prefix}/projects`} class="inline-flex h-10 items-center justify-center rounded-full bg-primary px-6 text-xs font-bold text-primary-foreground transition-all hover:bg-primary/90">
{t("home.featured.ctaPrimary")}
</a>
{project.links?.github && (
<a href={project.links.github} target="_blank" rel="noopener noreferrer" class="inline-flex h-10 w-10 items-center justify-center rounded-full border border-border bg-background/50 text-muted-foreground transition-all hover:bg-muted hover:text-foreground">
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 4.125 9.642 9.807 11.53 0.6.111 0.799-0.26 0.799-0.577 0-0.285-0.011-1.04-0.017-2.042-3.338 0.727-4.042-1.61-4.042-1.61-0.546-1.387-1.333-1.756-1.333-1.756-1.089-0.745 0.083-0.729 0.083-0.729 1.205 0.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492 0.997 0.107-0.775 0.418-1.305 0.762-1.604-2.665-0.305-5.467-1.334-5.467-5.931 0-1.311 0.469-2.381 1.236-3.221-0.124-0.303-0.535-1.524 0.117-3.176 0 0 1.008-0.322 3.301 1.23 0.96-0.267 1.98-0.399 3-0.405 1.02 0.006 2.04 0.138 3 0.405 2.28-1.552 3.285-1.23 3.285-1.23 0.654 1.653 0.242 2.874 0.118 3.176 0.77 0.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921 0.43 0.372 0.823 1.102 0.823 2.222 0 1.606-0.014 2.898-0.014 3.293 0 0.319 0.192 0.694 0.801 0.576 5.687-1.889 9.812-6.228 9.812-11.53 0-6.627-5.373-12-12-12z"/></svg>
</a>
)}
</div>
</div>
</article>
))}
</div>
</Container>
<div class="grid gap-6 lg:grid-cols-3">
{featuredProjects.map((project) => (
<article class="page-surface p-6">
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{project.systemType}</p>
<h3 class="mt-2 text-lg font-bold">{project.title}</h3>
<p class="mt-3 text-sm leading-relaxed text-muted-foreground">{project.context}</p>
<p class="mt-4 text-xs font-semibold uppercase tracking-wide text-foreground/70">{isZh ? '结果' : 'Outcome'}</p>
<p class="mt-1 text-sm text-foreground/90">{project.outcomes?.[0] ?? project.impact}</p>
</article>
))}
</div>
</section>
<section class="relative">
<Container>
<div class="flex flex-col items-center text-center">
<h2 class="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl mb-6">{t("home.final.title")}</h2>
<p class="text-lg leading-relaxed text-muted-foreground/90 sm:text-xl max-w-2xl mb-10">{t("home.final.description")}</p>
<div class="flex flex-wrap justify-center gap-4">
<a href={`${prefix}/about`} class="inline-flex h-12 items-center justify-center rounded-full bg-primary px-8 text-sm font-semibold text-primary-foreground transition-all hover:translate-y-[-2px] hover:bg-primary/90">
{t("home.final.ctaPrimary")}
</a>
<a href={`${prefix}/hire`} class="inline-flex h-12 items-center justify-center rounded-full border border-border px-8 text-sm font-semibold text-foreground transition-all hover:translate-y-[-2px] hover:bg-muted">
{t("home.final.ctaSecondary")}
</a>
</div>
</div>
</Container>
</section>
</div>
</div>
</main>
<section class="page-content-main mt-20">
<div class="mb-8 flex items-end justify-between gap-4">
<h2 class="text-2xl font-bold tracking-tight sm:text-3xl">{isZh ? '最新写作' : 'Latest Writing'}</h2>
<a href={`${prefix}/blog`} class="text-sm font-semibold text-primary hover:text-primary/80">{isZh ? '查看全部文章' : 'View All Writing'}</a>
</div>
<Footer lang={lang} client:only="react" />
<div class="grid gap-4 md:grid-cols-3">
{latestPosts.map((post) => (
<article class="page-surface p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{post.date}</p>
<h3 class="mt-2 text-base font-bold leading-snug">
<a href={`${prefix}/blog/posts/${post.slug}`} class="hover:text-primary">{post.title}</a>
</h3>
<p class="mt-2 line-clamp-3 text-sm text-muted-foreground">{post.description}</p>
</article>
))}
</div>
</section>
<section class="page-content-main mt-20 page-surface p-8">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '关于合作' : 'About Collaboration'}</h2>
<div class="mt-6 flex flex-wrap gap-3">
<a href={`${prefix}/about`} class="inline-flex h-10 items-center rounded-lg bg-primary px-5 text-sm font-semibold text-primary-foreground">{isZh ? '查看完整经历' : 'Read Full Profile'}</a>
<a href={`${prefix}/hire#contact-card`} class="inline-flex h-10 items-center rounded-lg border border-border px-5 text-sm font-semibold hover:bg-muted">{isZh ? '合作与联系' : 'Collaborate & Contact'}</a>
</div>
<p class="mt-4 text-xs text-muted-foreground">{personalInfo.name} · {personalInfo.position[lang]} · {personalInfo.location}</p>
</section>
</Container>
</main>
<Footer lang={lang} client:load />
</Layout>

View File

@@ -1,228 +1,67 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import { useTranslations } from "@/i18n/utils";
import { Hammer, Zap, Telescope, Bot, Target, Rocket, Sparkles, MessageSquare, Handshake, MapPin } from "lucide-react";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = lang === 'zh' ? '现在' : 'Now';
const lang = (Astro.currentLocale as Lang) || defaultLang;
const isZh = lang === 'zh';
const doing = isZh
? ['推进工程化案例化的个人站升级', '优化远程协作下的交付流程与文档标准', '持续打磨 AI 协作开发实践']
: ['Upgrading this portfolio into an engineering case-study site', 'Improving remote-first delivery workflow and documentation quality', 'Refining practical AI-assisted development workflows'];
const exploring = isZh
? ['复杂权限系统的前端边界设计', '大规模业务表单与状态流管理', '更稳定的跨团队异步协作机制']
: ['Frontend boundary design for complex permission systems', 'Scalable state management for large business forms', 'More stable async collaboration practices across teams'];
const shipping = isZh
? ['工程案例库结构重构', '导航与路由转化路径优化', '双语文案统一与信息层级简化']
: ['Refactoring project pages into engineering case studies', 'Optimizing navigation and conversion routes', 'Unifying EN/ZH copy and simplifying information hierarchy'];
---
<Layout title={pageTitle}>
<Layout title={isZh ? '现在' : 'Now'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<main class="min-h-screen relative overflow-hidden">
<!-- 背景装饰 -->
<div class="absolute inset-0 -z-10">
<div class="absolute left-1/2 top-0 h-[800px] w-[1000px] -translate-x-1/2 -translate-y-1/2 bg-primary/10 blur-[120px]"></div>
<div class="absolute right-0 top-1/3 h-[500px] w-[500px] bg-blue-500/5 blur-[100px]"></div>
</div>
<!-- Now Page Hero -->
<section class="pt-32 pb-24 relative overflow-hidden">
<div class="page-hero-overlay" aria-hidden="true"></div>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main">
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '现在' : 'Now'}</h1>
<p class="mt-4 text-lg text-muted-foreground">{isZh ? '我当前的工作重点与近期交付。' : 'What I am focusing on right now and what I am shipping recently.'}</p>
</section>
<Container className="relative z-10">
<div class="max-w-4xl mx-auto text-center">
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-6 animate-fade-in">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
</span>
{lang === 'zh' ? '实时状态' : "Live Status"}
</div>
<h1 class="page-title-gradient text-6xl md:text-8xl font-bold mb-8 tracking-tight">
{lang === 'zh' ? '现在' : 'Now'}
</h1>
<p class="text-xl md:text-2xl text-muted-foreground leading-relaxed max-w-2xl mx-auto">
{lang === 'zh'
? '关于我现在正在关注的事、正在构建的产品以及我的生活状态。'
: 'What I am currently focusing on, the products I am building, and my life status.'}
</p>
</div>
</Container>
</section>
<section class="page-content-main mt-10 grid gap-6 md:grid-cols-3">
<article class="page-surface p-6">
<h2 class="text-lg font-bold">{isZh ? '在做什么' : 'Doing'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
{doing.map((item) => <li>• {item}</li>)}
</ul>
</article>
<!-- Main Content -->
<section class="pb-32 relative mt-12 md:mt-24">
<Container>
<div class="max-w-4xl mx-auto space-y-24">
<!-- Building Section -->
<div class="space-y-10">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-primary/10 rounded-xl flex items-center justify-center border border-primary/20">
<Hammer className="w-6 h-6 text-primary" />
</div>
<h2 class="text-3xl md:text-4xl font-bold tracking-tight">
{lang === 'zh' ? '正在构建' : 'Building'}
</h2>
</div>
<article class="page-surface p-6">
<h2 class="text-lg font-bold">{isZh ? '在研究什么' : 'Exploring'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
{exploring.map((item) => <li>• {item}</li>)}
</ul>
</article>
<div class="group relative">
<div class="absolute -inset-1 rounded-[2rem] bg-gradient-to-r from-primary/30 to-blue-600/30 opacity-20 blur-xl transition duration-500 group-hover:opacity-40"></div>
<div class="page-surface relative p-8 md:p-12 overflow-hidden border-primary/10 rounded-[2rem]">
<div class="flex flex-col md:flex-row gap-10 items-start">
<div class="w-24 h-24 bg-gradient-to-br from-primary to-blue-600 rounded-2xl flex items-center justify-center flex-shrink-0 shadow-lg shadow-primary/20 group-hover:scale-105 transition-transform duration-500">
<Zap className="w-12 h-12 text-white" />
</div>
<div class="flex-1 space-y-6">
<div class="flex items-center justify-between">
<h3 class="text-3xl font-bold">Elynd</h3>
<span class="px-4 py-1.5 bg-green-500/10 text-green-600 dark:text-green-400 rounded-full text-xs font-bold border border-green-500/20 uppercase tracking-wider">Active</span>
</div>
<p class="text-xl text-muted-foreground leading-relaxed">
{lang === 'zh'
? '一个开放的 AI 工作空间,让构建者能够更智能地工作。从 AI 第一性原理出发,打造完全开放、可自托管的协作工具。'
: 'An open AI workspace for builders to work smarter. Built from first principles of AI, creating fully open, self-hostable collaboration tools.'}
</p>
<div class="flex flex-wrap gap-3 pt-2">
{['AI Product', 'TypeScript', 'Open Source', 'Astro'].map(tag => (
<span class="px-4 py-2 bg-secondary/50 text-secondary-foreground rounded-lg text-sm font-medium border border-border/50">{tag}</span>
))}
</div>
</div>
</div>
</div>
</div>
</div>
<article class="page-surface p-6">
<h2 class="text-lg font-bold">{isZh ? '最近交付' : 'Shipping'}</h2>
<ul class="mt-4 space-y-2 text-sm text-muted-foreground">
{shipping.map((item) => <li>• {item}</li>)}
</ul>
</article>
</section>
<!-- Exploring Section -->
<div class="space-y-10">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-blue-500/10 rounded-xl flex items-center justify-center border border-blue-500/20">
<Telescope className="w-6 h-6 text-blue-500" />
</div>
<h2 class="text-3xl md:text-4xl font-bold tracking-tight">
{lang === 'zh' ? '正在探索' : 'Exploring'}
</h2>
</div>
<div class="grid gap-8 md:grid-cols-2">
{[
{
title: lang === 'zh' ? 'AI Agents 与工作流自动化' : 'AI Agents & Workflow Automation',
desc: lang === 'zh' ? '研究如何构建能够自主完成复杂任务的 AI Agent以及如何将 AI 能力融入日常工作流。' : 'Researching how to build AI Agents that can autonomously complete complex tasks, and how to integrate AI capabilities into daily workflows.',
icon: Bot
},
{
title: lang === 'zh' ? '产品导向的开发实践' : 'Product-Led Development Practices',
desc: lang === 'zh' ? '探索从产品角度思考开发,构建真正解决用户问题的产品,而不仅仅是技术实现。' : 'Exploring product thinking in development, building products that truly solve user problems, not just technical implementations.',
icon: Target
}
].map(item => (
<div class="page-surface p-8 rounded-3xl hover:translate-y-[-4px] transition-all duration-300 border-border/50 group">
<div class="w-12 h-12 bg-muted rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300">
<item.icon className="w-6 h-6 text-foreground" />
</div>
<h3 class="text-2xl font-bold mb-4">{item.title}</h3>
<p class="text-muted-foreground text-base leading-relaxed">{item.desc}</p>
</div>
))}
</div>
</div>
<!-- Shipping Section -->
<div class="space-y-10">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-green-500/10 rounded-xl flex items-center justify-center border border-green-500/20">
<Rocket className="w-6 h-6 text-green-500" />
</div>
<h2 class="text-3xl md:text-4xl font-bold tracking-tight">
{lang === 'zh' ? '最近发布' : 'Shipping'}
</h2>
</div>
<div class="space-y-6">
{[
{
title: lang === 'zh' ? '个人网站全新改版' : 'Portfolio Redesign',
desc: lang === 'zh' ? '基于 Astro 5 和 Tailwind 4 的高性能双语站点。' : 'High-performance bilingual site built with Astro 5 & Tailwind 4.',
icon: Sparkles,
color: 'bg-green-500/10',
iconColor: 'text-green-500'
},
{
title: lang === 'zh' ? 'DeepSeek 满血版接入' : 'DeepSeek Full Access',
desc: lang === 'zh' ? '在本地工作流中深度整合 DeepSeek-V3 提升效率。' : 'Deep integration of DeepSeek-V3 in local workflows for efficiency.',
icon: MessageSquare,
color: 'bg-blue-500/10',
iconColor: 'text-blue-500'
}
].map(item => (
<div class="group relative flex items-center gap-6 p-4 rounded-2xl hover:bg-muted/50 transition-all duration-300">
<div class={`w-12 h-12 ${item.color} rounded-xl flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform`}>
<item.icon className={`w-6 h-6 ${item.iconColor}`} />
</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-lg group-hover:text-primary transition-colors truncate">
{item.title}
</h3>
<p class="text-sm text-muted-foreground line-clamp-1">
{item.desc}
</p>
</div>
<div class="text-muted-foreground/30 group-hover:text-primary/50 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</div>
</div>
))}
</div>
</div>
<!-- Bottom Grid: Collaboration & Meta -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 pt-8">
<!-- Collaboration Card -->
<div class="relative group h-full">
<div class="absolute -inset-1 rounded-[2.5rem] bg-gradient-to-br from-yellow-500/20 to-orange-500/20 blur opacity-75"></div>
<div class="page-surface relative p-10 border-yellow-500/10 bg-gradient-to-br from-card to-yellow-500/5 rounded-[2.5rem] h-full flex flex-col justify-between">
<div>
<div class="w-14 h-14 bg-yellow-500/10 rounded-2xl flex items-center justify-center mb-8">
<Handshake className="w-8 h-8 text-yellow-600 dark:text-yellow-400" />
</div>
<h3 class="text-3xl font-bold mb-4">{lang === 'zh' ? '开放合作' : 'Collaborate'}</h3>
<p class="text-lg text-muted-foreground mb-8 leading-relaxed">
{lang === 'zh'
? '始终对有趣的产品想法 and 技术咨询持开放态度。'
: "Always open to interesting product ideas and technical consulting."}
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<a href={lang === 'zh' ? '/zh/hire' : '/hire'} class="flex items-center justify-center py-4 rounded-2xl bg-primary text-primary-foreground font-bold text-base hover:scale-[1.02] transition-transform">
{lang === 'zh' ? '合作方式' : 'View Options'}
</a>
<a href="mailto:zhaoguiyang18@gmail.com" class="flex items-center justify-center py-4 rounded-2xl border border-border bg-background/50 text-foreground font-bold text-base hover:bg-muted transition-colors">
{lang === 'zh' ? '发送邮件' : 'Email Me'}
</a>
</div>
</div>
</div>
<!-- Meta/Status Card -->
<div class="page-surface p-10 border-border/50 rounded-[2.5rem] flex flex-col items-center justify-center text-center space-y-6">
<div class="w-20 h-20 bg-muted rounded-full flex items-center justify-center text-3xl">
<MapPin className="w-10 h-10 text-primary" />
</div>
<div class="space-y-2">
<h3 class="text-2xl font-bold">{lang === 'zh' ? '当前位置' : 'Location'}</h3>
<p class="text-muted-foreground">{lang === 'zh' ? '中国 · 杭州' : 'Hangzhou, China'}</p>
</div>
<div class="pt-4 w-full">
<div class="inline-flex items-center gap-3 px-5 py-2.5 rounded-full bg-muted/50 border border-border/50 mx-auto">
<span class="text-xs text-muted-foreground uppercase font-bold tracking-widest border-r border-border/50 pr-3">
{lang === 'zh' ? '最后更新' : 'Updated'}
</span>
<span class="text-sm font-semibold">2025.03.14</span>
</div>
</div>
</div>
</div>
</div>
</Container>
</section>
<section class="page-content-main mt-8 page-surface p-6">
<p class="text-sm text-muted-foreground">
{isZh ? '最后更新2026-03-16' : 'Last updated: 2026-03-16'}
</p>
</section>
</Container>
</main>
<Footer lang={lang} client:only="react" />
<Footer lang={lang} client:load />
</Layout>

View File

@@ -1,114 +1,111 @@
---
import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer";
import Container from "@/components/ui/Container.astro";
import ProjectCard from "@/components/ProjectCard.astro";
import { useTranslations } from "@/i18n/utils";
import type { Lang } from "@/types/i18n";
import { defaultLang } from "@/i18n/ui";
import { projects } from "@/lib/data/index";
import Layout from '@/layouts/Layout.astro';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import Container from '@/components/ui/Container.astro';
import { projects } from '@/lib/data';
import type { Lang } from '@/types/i18n';
import { defaultLang } from '@/i18n/ui';
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = t('projects.title');
// 根据当前语言获取项目数据
const currentProjects = projects[lang as keyof typeof projects] || projects.en;
const filterOptions = [
{ key: "all", label: t("project.filter.all") },
{ key: "product", label: t("project.type.product") },
{ key: "client", label: t("project.type.client") },
{ key: "experiment", label: t("project.type.experiment") },
];
const lang = (Astro.currentLocale as Lang) || defaultLang;
const isZh = lang === 'zh';
const pageProjects = projects[lang].map((project) => ({
...project,
category: project.id === 'elynd' ? 'indie' : 'enterprise',
}));
const enterpriseProjects = pageProjects.filter((project) => project.category === 'enterprise');
const indieProjects = pageProjects.filter((project) => project.category === 'indie');
---
<Layout title={pageTitle}>
<Layout title={isZh ? '项目' : 'Projects'}>
<GlassHeader lang={lang} client:load transition:persist="header" />
<main class="min-h-screen relative overflow-hidden">
<div
aria-hidden="true"
class="page-hero-overlay"
></div>
<section class="relative z-10 py-24">
<Container>
<div class="text-center mb-8">
<h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
{t('projects.title')}
</h1>
<p class="text-lg text-muted-foreground page-content-main mb-12 leading-relaxed">
{t('projects.slogan')} {t('projects.description')}
</p>
<main class="min-h-screen pt-24 pb-20">
<Container>
<section class="page-content-main">
<h1 class="text-4xl font-bold tracking-tight sm:text-5xl">{isZh ? '项目经历' : 'Project Experience'}</h1>
<p class="mt-4 text-lg leading-relaxed text-muted-foreground">
{isZh
? '按两类展示:企业项目与独立开发项目。采用卡片布局,聚焦项目背景、职责和结果。'
: 'Organized into two categories: enterprise projects and independent projects, shown in a cover-style card grid.'}
</p>
</section>
<section class="page-content-main mt-8">
<div class="mb-5 flex items-end justify-between gap-3">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '企业项目' : 'Enterprise Projects'}</h2>
<span class="text-sm text-muted-foreground">{enterpriseProjects.length} {isZh ? '个项目' : 'projects'}</span>
</div>
</Container>
</section>
<section class="relative z-10 pb-20" data-projects-root>
<Container>
<div class="page-content-main">
<div class="page-surface flex flex-wrap gap-2 mb-8 p-1 rounded-xl">
{filterOptions.map((option, index) => (
<button
type="button"
data-filter={option.key}
aria-pressed={index === 0 ? "true" : "false"}
class={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
index === 0
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground hover:bg-muted"
}`}
>
{option.label}
</button>
))}
</div>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{currentProjects.map((project) => (
<ProjectCard project={project} lang={lang} showStatus={true} />
))}
</div>
<div class="grid gap-5 sm:grid-cols-2 xl:grid-cols-3">
{enterpriseProjects.map((project) => (
<article class="page-surface overflow-hidden">
<div class={`bg-gradient-to-br ${project.image.bg} p-5`}>
<p class="text-xs font-semibold uppercase tracking-wider text-foreground/80">{isZh ? '企业项目' : 'Enterprise'}</p>
<h3 class="mt-2 text-lg font-bold">{project.title}</h3>
<p class="mt-2 text-sm text-foreground/80">{project.systemType}</p>
</div>
<div class="p-5">
<p class="text-sm text-muted-foreground">{project.context}</p>
<div class="mt-4 flex flex-wrap gap-2">
{project.tech.slice(0, 5).map((tech) => (
<span class="rounded-md border border-border bg-muted/50 px-2.5 py-1 text-xs font-medium">{tech}</span>
))}
</div>
<p class="mt-4 text-sm font-semibold text-foreground/90">{isZh ? '结果:' : 'Outcome:'} {project.outcomes?.[0] ?? project.impact}</p>
<p class="mt-3 text-xs text-muted-foreground">{isZh ? '受保密协议限制,暂不提供在线预览。' : 'Online preview is not available due to confidentiality restrictions.'}</p>
</div>
</article>
))}
</div>
</Container>
</section>
</section>
<section class="page-content-main mt-12">
<div class="mb-5 flex items-end justify-between gap-3">
<h2 class="text-2xl font-bold tracking-tight">{isZh ? '独立项目' : 'Independent Projects'}</h2>
<span class="text-sm text-muted-foreground">{indieProjects.length} {isZh ? '个项目' : 'projects'}</span>
</div>
<div class="grid gap-5 sm:grid-cols-2 xl:grid-cols-3">
{indieProjects.map((project) => (
<article class="page-surface overflow-hidden">
<div class="relative aspect-[16/9] overflow-hidden border-b border-border/70 bg-muted/30">
{project.coverImage ? (
<img
src={project.coverImage}
alt={project.coverImageAlt || project.title}
class="h-full w-full object-cover"
loading="lazy"
decoding="async"
/>
) : (
<div class={`flex h-full w-full flex-col justify-end bg-gradient-to-br ${project.image.bg} p-5`}>
<p class="text-xs font-semibold uppercase tracking-wider text-foreground/80">{isZh ? '封面待补充' : 'Cover Coming Soon'}</p>
<h3 class="mt-2 text-lg font-bold">{project.title}</h3>
<p class="mt-1 text-sm text-foreground/80">{project.systemType}</p>
</div>
)}
</div>
<div class="p-5">
<p class="text-xs font-semibold uppercase tracking-wider text-primary/80">{isZh ? '独立项目' : 'Independent'}</p>
<h3 class="mt-2 text-lg font-bold">{project.title}</h3>
<p class="text-sm text-muted-foreground">{project.context}</p>
<div class="mt-4 flex flex-wrap gap-2">
{project.tech.slice(0, 5).map((tech) => (
<span class="rounded-md border border-border bg-muted/50 px-2.5 py-1 text-xs font-medium">{tech}</span>
))}
</div>
<p class="mt-4 text-sm font-semibold text-foreground/90">{isZh ? '结果:' : 'Outcome:'} {project.outcomes?.[0] ?? project.impact}</p>
{project.link !== '#' && (
<a href={project.link} target="_blank" rel="noopener noreferrer" class="mt-3 inline-flex text-sm font-semibold text-primary hover:text-primary/80">
{isZh ? '查看项目' : 'Open Project'}
</a>
)}
</div>
</article>
))}
</div>
</section>
</Container>
</main>
<Footer lang={lang} client:load />
</Layout>
<script>
const projectsRoot = document.querySelector("[data-projects-root]");
if (projectsRoot) {
const filterButtons = Array.from(projectsRoot.querySelectorAll("[data-filter]"));
const projectCards = Array.from(projectsRoot.querySelectorAll("[data-project-card]"));
const applyFilter = (activeFilter) => {
projectCards.forEach((card) => {
const projectType = card.getAttribute("data-type");
const shouldShow = activeFilter === "all" || projectType === activeFilter;
card.classList.toggle("hidden", !shouldShow);
});
filterButtons.forEach((button) => {
const isActive = button.getAttribute("data-filter") === activeFilter;
button.setAttribute("aria-pressed", isActive ? "true" : "false");
button.classList.toggle("bg-primary", isActive);
button.classList.toggle("text-primary-foreground", isActive);
button.classList.toggle("text-muted-foreground", !isActive);
button.classList.toggle("hover:text-foreground", !isActive);
button.classList.toggle("hover:bg-muted", !isActive);
});
};
filterButtons.forEach((button) => {
button.addEventListener("click", () => {
const selectedFilter = button.getAttribute("data-filter") ?? "all";
applyFilter(selectedFilter);
});
});
applyFilter("all");
}
</script>

View File

@@ -1,42 +1,14 @@
---
title: "我能做什么"
description: "专注于构建 AI 产品和技术卓越的专业服务"
title: "页面已迁移"
description: "该页面已迁移到新的结构。"
layout: "../../layouts/AboutLayout.astro"
---
import HighlightBox from '../../components/markdown/HighlightBox.astro';
# 页面已迁移
# 我能做什么 🚀
原 **服务** 页面已拆分为更清晰的页面:
<HighlightBox type="tip">
构建 AI 驱动产品,将雄心勃勃的想法变为现实。
</HighlightBox>
- [工具](/zh/uses)
- [关于(联系卡片)](/zh/about#contact-card)
## AI 产品开发 🤖
- 从概念到上线构建 AI 驱动产品
- RAG 应用、AI Agent 和智能自动化
- 使用现代技术栈的全栈开发React、Next.js、Node.js
- 产品思维 - 专注于解决实际问题
- 快速原型和迭代开发
## 技术咨询 📊
<HighlightBox type="info" title="我可以如何帮助">
- 技术架构和技术栈决策
- 代码审查和最佳实践实施
- 团队指导和知识转移
- 性能优化和扩展策略
- 投资技术尽职调查
</HighlightBox>
# 一起构建 💼
<HighlightBox type="success" title="联系我">
我始终对讨论新项目和机会持开放态度。无论你是正在寻找技术合伙人、需要帮助构建 AI 产品,还是想要技术咨询 - 让我们聊聊。
- 邮箱:**zhaoguiyang18@gmail.com**
- 查看我的[合作](/zh/hire)页面了解合作方式
</HighlightBox>
如需沟通远程岗位,请优先查看 [合作](/zh/hire),并使用页面内的联系卡片。

3
src/pages/zh/uses.astro Normal file
View File

@@ -0,0 +1,3 @@
---
return Astro.redirect('/zh/about#uses', 301);
---

View File

@@ -43,14 +43,30 @@ export interface ProjectImage {
export interface Project {
id: string;
tag: string;
tag?: string;
title: string;
icon: string;
type?: string;
status?: string;
role?: string;
impact?: string;
systemType?: string;
context?: string;
challenges?: string[];
responsibilities?: string[];
outcomes?: string[];
color: string;
image: ProjectImage;
description: string[];
tech: string[];
link: string;
featured?: boolean;
coverImage?: string;
coverImageAlt?: string;
links?: {
github?: string;
demo?: string;
};
}
export interface ServiceIcon {
@@ -63,4 +79,21 @@ export interface Service {
icon: ServiceIcon;
items: string[];
color: string;
}
}
export interface UsesGroup {
title: LocalizedText;
items: string[];
}
export interface ContactIntent {
id: string;
title: LocalizedText;
description: LocalizedText;
}
export interface ContactMethod {
label: LocalizedText;
value: string;
href?: string;
}

257
优化.md Normal file
View File

@@ -0,0 +1,257 @@
# Personal Website Rebuild Plan (Advanced Engineer Version)
Author: Zhao Guiyang
Website: https://zhaoguiyang.com
Goal:
Transform the website from a generic portfolio into an **engineering-focused personal site** that communicates real experience, technical thinking, and current work.
The site should resemble professional developer sites such as those of senior engineers, not template demo portfolios.
---
# 1 Core Positioning
Professional identity to communicate:
Senior Frontend Engineer
Full-stack Developer
AI-assisted Development Practitioner
Key signals:
- 8 years web development experience
- Complex enterprise system development
- Financial and blockchain infrastructure platforms
- Frontend architecture and engineering practices
- AI-assisted development workflows
Avoid generic descriptions such as "passionate developer".
Focus on **real engineering capability**.
---
# 2 Website Structure
The site should contain the following pages.
Home
About
Projects
Writing
Now
Uses
Contact
Each page must be concise and information-dense.
---
# 3 Home Page
Purpose: immediate identity signal.
Structure:
Name
Short identity line:
Senior Frontend Engineer building complex systems and AI-assisted products.
Short introduction (23 sentences):
Zhao Guiyang is a web developer with 8 years of experience building financial platforms, enterprise systems, and blockchain infrastructure.
He focuses on frontend architecture, complex system design, and AI-assisted development workflows.
Links section:
Projects
Writing
GitHub
Contact
---
# 4 About Page
This page explains the developer's background and technical focus.
Sections:
Background
Zhao Guiyang is a web developer with extensive experience building enterprise systems and financial platforms.
His work focuses on frontend engineering architecture and complex business systems.
Technical Focus
Frontend architecture
Large-scale enterprise applications
Financial and blockchain systems
AI-assisted development workflows
Experience
8 years of professional development experience.
Participation in multiple government and financial technology systems including trading platforms, blockchain infrastructure, and industrial management systems.
---
# 5 Projects Page (Engineering Case Studies)
Replace demo projects with real engineering work.
Each project should include:
Project name
System type
Description
Technical challenges
Responsibilities
Results
Example structure:
Project: Digital Securities Trading Platform
System Type:
Financial blockchain trading platform
Description:
A digital asset trading system supporting multi-role financial institutions.
Technical Challenges:
Complex role-based permissions
Financial system stability requirements
Blockchain wallet integration
Responsibilities:
Frontend module architecture
User management system refactor
Bank login integration
Results:
System stability reached 99.5% uptime.
---
Include these real projects:
Digital Securities Trading Platform
Blockchain BaaS Platform
Supply Chain Finance Platform
Industrial Smart Park Management System
Do not include demo projects.
---
# 6 Writing Page
Purpose: demonstrate technical thinking.
Blog topics should focus on engineering experience.
Examples:
Designing complex permission systems
Optimizing large file uploads
Frontend architecture for financial platforms
AI-assisted development workflow
Avoid beginner tutorials.
---
# 7 Now Page
Purpose: show current activity.
Content example:
Currently exploring AI-assisted software development.
Building developer tools and experimenting with AI product ideas.
Working on open-source tools and automation.
---
# 8 Uses Page
List tools used in daily development.
Example:
Editor:
VS Code
Languages:
TypeScript
JavaScript
Frameworks:
React
Vue3
Tools:
Git
Docker
Node.js
AI Tools:
ChatGPT
Claude
AI-assisted development workflows
---
# 9 Contact Page
Provide minimal contact methods.
Email
GitHub
LinkedIn (optional)
Avoid excessive personal information.
---
# 10 Design Principles
The website should follow these rules:
Minimalist design
Text-focused content
Fast loading
No heavy animations
No unnecessary UI complexity
Content clarity is more important than visual effects.
---
# 11 Execution Rules for AI
The executing AI must follow these rules:
Do not fabricate projects.
Do not invent work experience.
Do not add technologies not present in the resume.
Maintain concise technical writing style.
Preserve the current UI layout if possible.
Only update textual content and project listings.
---
# End of Document